diff --git a/.gitignore b/.gitignore index d22760e778069498ed0e86a4bccbcb06f870a28a..94a210b94612ae613a7f677ed731da6fa847e334 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ db/data.yml .idea .DS_Store .chef +vendor/bundle/* diff --git a/.travis.yml b/.travis.yml index 868a6c6c238fcb2fe2d3faa232dceee0594521ab..ad00ded07cbc20020318bf15a81804c82ca5358c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,7 @@ services: before_script: - "cp config/database.yml.$DB config/database.yml" - "cp config/gitlab.yml.example config/gitlab.yml" - - "bundle exec rake db:create RAILS_ENV=test" - - "bundle exec rake db:migrate RAILS_ENV=test" + - "bundle exec rake db:setup RAILS_ENV=test" - "bundle exec rake db:seed_fu RAILS_ENV=test" - "sh -e /etc/init.d/xvfb start" script: "bundle exec rake travis --trace" diff --git a/CHANGELOG b/CHANGELOG index 2eca1f14c4f940181c3a0b7cade93c0a79087cc3..dda12bd11deb43d7a33cf10a40ac688850aab325 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,35 @@ +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 + - Reorganized settings + - Fixed commits compare + - Refactored scss + - Improve status checks + - Validates presence of User#name + - Fixed postgres support + - Removed sqlite support + - Modified post-receive hook + - Milestones can be closed now + - Show comment events on dashboard + - Quick add team members via group#people page + - [API] expose created date for hooks and SSH keys + - [API] list, create issue notes + - [API] list, create snippet notes + - [API] list, create wall notes + - Remove project code - use path instead + - added username field to user + - rake task to fill usernames based on emails create namespaces for users + - STI Group < Namespace + - Project has namespace_id + - Projects with namespaces also namespaced in gitolite and stored in subdir + - Moving project to group will move it under group namespace + - Ability to move project from namespaces to another + - Fixes commit patches getting escaped (see #2036) + - Support diff and patch generation for commits and merge request + - MergeReqest doesn't generate a temporary file for the patch any more + - Update the UI to allow downloading Patch or Diff + v 3.1.0 - Updated gems - Services: Gitlab CI integration @@ -34,7 +66,7 @@ v 3.0.0 - Fixed bug with gitolite keys - UI improved - Increased perfomance of application - - Show user avatar in last commit when browsing Files + - Show user avatar in last commit when browsing Files - Refactored Gitlab::Merge - Use Font Awsome for icons - Separate observing of Note and MergeRequestsa diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f8314462a2bf5ffc23bb23f5805dbe12db8ba9a..00304dd3d64e47e73a9479b3bfc5004c87e81c1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,26 @@ -## Contribute to GitLab +# Contact & support -If you want to contribute to GitLab, follow this process: +If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq). +Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues. -1. Fork the project -2. Create a feature branch -3. Code -4. Create a pull request -We will only accept pull requests if: -* Your code has proper tests and all tests pass -* Your code can be merged w/o problems -* It won't break existing functionality -* It's quality code -* We like it :) +# Contribute to GitLab -For examples of feedback on pull requests please look at the [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed). +## Recipes -## Installation +We collect user submitted installation scripts and config file templates for platforms we don't support officially. +We believe there is merit in allowing a certain amount of diversity. +You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc. -Install the Gitlab development in a virtual machine with the [Gitlab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm). Installing it in a virtual machine makes it much easier to set up all the dependencies for integration testing. +Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/) -## Running tests -For more information on running the tests please read the [development tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md) +## Feature suggestions + +Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own. + + +## Code + +Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab. diff --git a/Gemfile b/Gemfile index f723f587fbc037806250d7e659bb01e7acbca5cd..49fbcad04fcdbb0d9a46c79e9185e10013c00f33 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,6 @@ end gem "rails", "3.2.9" # Supported DBs -gem "sqlite3", group: :sqlite gem "mysql2", group: :mysql gem "pg", group: :postgres @@ -27,13 +26,13 @@ gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: gem "omniauth-ldap", git: "https://github.com/gitlabhq/omniauth-ldap.git", ref: 'f038dd852d7bd473a557e385d5d7c2fd5dc1dc2e' gem 'yaml_db', git: "https://github.com/gitlabhq/yaml_db.git", ref: '98e9a5dca43e3fedd3268c76a73af40d1bdf1dfd' gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8' -gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '212fd40bea61f3c6a167223768e7295dc32bbc10' +gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e' # Gitolite client (for work with gitolite-admin repo) gem "gitolite", '1.1.0' # Syntax highlighter -gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", ref: '4db80c599067e2d5f23c5c243bf85b8ca0368ad4' +gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master" # Language detection gem "github-linguist", "~> 2.3.4" , require: "linguist" @@ -101,11 +100,11 @@ group :assets do gem "therubyracer" gem 'chosen-rails', "0.9.8" - gem 'jquery-atwho-rails', "0.1.6" + gem 'jquery-atwho-rails', "0.1.7" gem "jquery-rails", "2.1.3" gem "jquery-ui-rails", "2.0.2" gem "modernizr", "2.6.2" - gem "raphael-rails", "2.1.0" + gem "raphael-rails", "1.5.2" gem 'bootstrap-sass', "2.2.1.1" gem "font-awesome-sass-rails", "~> 2.0.0" gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' @@ -125,7 +124,7 @@ group :development, :test do gem "capybara" gem "pry" gem "awesome_print" - gem "database_cleaner" + gem "database_cleaner", ref: "f89c34300e114be99532f14c115b2799a3380ac6", git: "https://github.com/bmabey/database_cleaner.git" gem "launchy" gem 'factory_girl_rails' @@ -139,7 +138,7 @@ group :development, :test do gem 'rb-inotify', require: linux_only('rb-inotify') # PhantomJS driver for Capybara - gem 'poltergeist' + gem 'poltergeist', git: 'https://github.com/jonleighton/poltergeist.git', ref: '5c2e092001074a8cf09f332d3714e9ba150bc8ca' end group :test do @@ -152,5 +151,5 @@ group :test do end group :production do - gem "gitlab_meta", '3.1' + gem "gitlab_meta", '4.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 0e3a9810dbe78bbaa6d53f77c548df270b2b98a7..d8be14ba80ab191d6001e601026d27cdfa8fbb54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/bmabey/database_cleaner.git + revision: f89c34300e114be99532f14c115b2799a3380ac6 + ref: f89c34300e114be99532f14c115b2799a3380ac6 + specs: + database_cleaner (0.9.1) + GIT remote: https://github.com/ctran/annotate_models.git revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e @@ -26,10 +33,10 @@ GIT GIT remote: https://github.com/gitlabhq/grit_ext.git - revision: 212fd40bea61f3c6a167223768e7295dc32bbc10 - ref: 212fd40bea61f3c6a167223768e7295dc32bbc10 + revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e + ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e specs: - grit_ext (0.6.0) + grit_ext (0.6.1) charlock_holmes (~> 0.6.9) GIT @@ -45,8 +52,8 @@ GIT GIT remote: https://github.com/gitlabhq/pygments.rb.git - revision: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4 - ref: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4 + revision: db1da0343adf86b49bdc3add04d02d2e80438d38 + branch: master specs: pygments.rb (0.3.2) posix-spawn (~> 0.3.6) @@ -59,6 +66,18 @@ GIT specs: yaml_db (0.2.2) +GIT + remote: https://github.com/jonleighton/poltergeist.git + revision: 5c2e092001074a8cf09f332d3714e9ba150bc8ca + ref: 5c2e092001074a8cf09f332d3714e9ba150bc8ca + specs: + poltergeist (1.0.2) + capybara (~> 1.1) + childprocess (~> 0.3) + faye-websocket (~> 0.4, >= 0.4.4) + http_parser.rb (~> 0.5.3) + multi_json (~> 1.0) + GEM remote: http://rubygems.org/ specs: @@ -128,7 +147,6 @@ GEM colorize (0.5.8) crack (0.3.1) daemons (1.1.9) - database_cleaner (0.9.1) devise (2.1.2) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) @@ -171,7 +189,7 @@ GEM mime-types (~> 1.19) pygments.rb (>= 0.2.13) github-markup (0.7.4) - gitlab_meta (3.1) + gitlab_meta (4.0) gitolite (1.1.0) gratr19 (~> 0.4.4.1) grit (~> 2.5.0) @@ -215,7 +233,7 @@ GEM httpauth (0.2.0) i18n (0.6.1) journey (1.0.4) - jquery-atwho-rails (0.1.6) + jquery-atwho-rails (0.1.7) jquery-rails (2.1.3) railties (>= 3.1.0, < 5.0) thor (~> 0.14) @@ -279,12 +297,6 @@ GEM omniauth-oauth (~> 1.0) orm_adapter (0.4.0) pg (0.14.1) - poltergeist (1.0.2) - capybara (~> 1.1) - childprocess (~> 0.3) - faye-websocket (~> 0.4, >= 0.4.4) - http_parser.rb (~> 0.5.3) - multi_json (~> 1.0) polyglot (0.3.3) posix-spawn (0.3.6) pry (0.9.10) @@ -329,7 +341,7 @@ GEM thor (>= 0.14.6, < 2.0) raindrops (0.10.0) rake (10.0.1) - raphael-rails (2.1.0) + raphael-rails (1.5.2) rb-fsevent (0.9.2) rb-inotify (0.8.8) ffi (>= 0.5.0) @@ -404,7 +416,6 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.6) stamp (0.3.0) test_after_commit (0.0.1) therubyracer (0.10.2) @@ -453,7 +464,7 @@ DEPENDENCIES chosen-rails (= 0.9.8) coffee-rails (~> 3.2.2) colored - database_cleaner + database_cleaner! devise (~> 2.1.0) draper (~> 0.18.0) email_spec @@ -465,7 +476,7 @@ DEPENDENCIES git github-linguist (~> 2.3.4) github-markup (~> 0.7.4) - gitlab_meta (= 3.1) + gitlab_meta (= 4.0) gitolite (= 1.1.0) grack! grape (~> 0.2.1) @@ -476,7 +487,7 @@ DEPENDENCIES guard-spinach haml-rails (~> 0.3.5) httparty - jquery-atwho-rails (= 0.1.6) + jquery-atwho-rails (= 0.1.7) jquery-rails (= 2.1.3) jquery-ui-rails (= 2.0.2) kaminari (~> 0.14.1) @@ -490,14 +501,14 @@ DEPENDENCIES omniauth-ldap! omniauth-twitter pg - poltergeist + poltergeist! pry pygments.rb! quiet_assets (~> 1.0.1) rack-mini-profiler rails (= 3.2.9) rails-dev-tweaks - raphael-rails (= 2.1.0) + raphael-rails (= 1.5.2) rb-fsevent rb-inotify redcarpet (~> 2.2.2) @@ -512,7 +523,6 @@ DEPENDENCIES simplecov six spinach-rails - sqlite3 stamp test_after_commit therubyracer diff --git a/README.md b/README.md index 1816629d3aafb21740d92b65705b61bde7d5509f..629fcefcb32f54f92ed83eb24591bf5ee5abe014 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://secure.travis-ci.org/gitlabhq/gitlabhq) [![build status](https://secure.travis-ci.org/gitlabhq/grit.png)](https://secure.travis-ci.org/gitlabhq/grit) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) +# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) [![build status](https://secure.travis-ci.org/gitlabhq/grit.png)](https://travis-ci.org/gitlabhq/grit) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) GitLab is a free project and repository management application diff --git a/VERSION b/VERSION index fd2a01863fdd3035fac5918c59666363544bfe23..fcdb2e109f68cff5600955a73908885fe8599bb4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.0 +4.0.0 diff --git a/app/assets/fonts/korolev-medium-compressed.otf b/app/assets/fonts/korolev-medium-compressed.otf index e3817cec85765374e2853696aca7c889a849fcd0..a9cd3cbffffa31137946ae9f7ebea43f0de9b3c6 100644 Binary files a/app/assets/fonts/korolev-medium-compressed.otf and b/app/assets/fonts/korolev-medium-compressed.otf differ diff --git a/app/assets/images/ajax_loader_gray.gif b/app/assets/images/ajax_loader_gray.gif new file mode 100644 index 0000000000000000000000000000000000000000..af3f618bd0b735578935242a4d5a5e23906b50c8 Binary files /dev/null and b/app/assets/images/ajax_loader_gray.gif differ diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png deleted file mode 100644 index 2d08c9f682593351b754d46466117222b64ff260..0000000000000000000000000000000000000000 Binary files a/app/assets/images/logo.png and /dev/null differ diff --git a/app/assets/images/logo_basic.png b/app/assets/images/logo_basic.png deleted file mode 100644 index bc5ec128b90ab7741a1dab22c28ff822336d6249..0000000000000000000000000000000000000000 Binary files a/app/assets/images/logo_basic.png and /dev/null differ diff --git a/app/assets/images/logo_text.png b/app/assets/images/logo_text.png deleted file mode 100644 index c74663930e4a0073f92bedfd02a0929879e59d25..0000000000000000000000000000000000000000 Binary files a/app/assets/images/logo_text.png and /dev/null differ diff --git a/app/assets/images/logo_text_tr.png b/app/assets/images/logo_text_tr.png deleted file mode 100644 index fdb32ee29fe17975fbb229c828458819aaf3ca42..0000000000000000000000000000000000000000 Binary files a/app/assets/images/logo_text_tr.png and /dev/null differ diff --git a/app/assets/images/logo_white.png b/app/assets/images/logo_white.png index 366e3f3f3b9f16a15f43a7f3fdd04aa2470a177f..e34158165581eea90fed59a4ff494852ebdc9769 100644 Binary files a/app/assets/images/logo_white.png and b/app/assets/images/logo_white.png differ diff --git a/app/assets/images/service-disabled-gitlab-ci.png b/app/assets/images/service-disabled-gitlab-ci.png deleted file mode 100644 index 8d1f9d0b50d01604383045d307369096fbfbda7f..0000000000000000000000000000000000000000 Binary files a/app/assets/images/service-disabled-gitlab-ci.png and /dev/null differ diff --git a/app/assets/images/service-gitlab-ci.png b/app/assets/images/service-gitlab-ci.png deleted file mode 100644 index bcb30a3fb1ada06b21c05de1814b7b6bea9e7652..0000000000000000000000000000000000000000 Binary files a/app/assets/images/service-gitlab-ci.png and /dev/null differ diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7c11f20659367c4f3371c27d9f1b2a37cf89dab9 Binary files /dev/null and b/app/assets/images/switch_icon.png differ diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 76454c29269bc1e5d55ab26fca38cb46e240fdfc..1dafdf4bd8b8aebfe0661235d7a3608e85361d4c 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -10,3 +10,8 @@ $ -> $('.log-tabs a').click (e) -> e.preventDefault() $(this).tab('show') + + $('.log-bottom').click (e) -> + e.preventDefault() + visible_log = $(".file_content:visible") + visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast") diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index ffc4c409b5421be9f093f061cc8d219c2447ab70..1cc9d34dd80a9f1274e487605bec3eeafd688714 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -1,52 +1,38 @@ # Creates the variables for setting up GFM auto-completion window.GitLab ?= {} -GitLab.GfmAutoComplete ?= {} - -# Emoji -data = [] -template = "<li data-value='${insert}'>${name} <img alt='${name}' height='20' src='${image}' width='20' /></li>" -GitLab.GfmAutoComplete.Emoji = {data, template} - -# Team Members -data = [] -url = ''; -params = {private_token: '', page: 1} -GitLab.GfmAutoComplete.Members = {data, url, params} - -# Add GFM auto-completion to all input fields, that accept GFM input. -GitLab.GfmAutoComplete.setup = -> - input = $('.js-gfm-input') - +GitLab.GfmAutoComplete = # Emoji - input.atWho ':', - data: GitLab.GfmAutoComplete.Emoji.data, - tpl: GitLab.GfmAutoComplete.Emoji.template + Emoji: + data: [] + template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>' # Team Members - input.atWho '@', (query, callback) -> - (getMoreMembers = -> - $.getJSON(GitLab.GfmAutoComplete.Members.url, GitLab.GfmAutoComplete.Members.params) - .success (members) -> - # pick the data we need - newMembersData = $.map(members, (m) -> m.name ) - - # add the new page of data to the rest - $.merge(GitLab.GfmAutoComplete.Members.data, newMembersData) - - # show the pop-up with a copy of the current data - callback(GitLab.GfmAutoComplete.Members.data[..]) - - # are we past the last page? - if newMembersData.length is 0 - # set static data and stop callbacks - input.atWho '@', - data: GitLab.GfmAutoComplete.Members.data - callback: null - else - # get next page - getMoreMembers() + Members: + data: [] + url: '' + params: + private_token: '' + template: '<li data-value="${username}">${username} <small>${name}</small></li>' + + # Add GFM auto-completion to all input fields, that accept GFM input. + setup: -> + input = $('.js-gfm-input') + + # Emoji + input.atWho ':', + data: @Emoji.data + tpl: @Emoji.template + + # Team Members + input.atWho '@', + tpl: @Members.template + callback: (query, callback) => + request_params = $.extend({}, @Members.params, query: query) + $.getJSON(@Members.url, request_params).done (members) => + new_members_data = $.map(members, (m) -> + username: m.username, + name: m.name + ) + callback(new_members_data) - # so the next request gets the next page - GitLab.GfmAutoComplete.Members.params.page += 1 - ).call() diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index e2fe107590321496a283ed485e0b07da17d3d97d..719d2c176c1361c7feccc66af57165c607a27914 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -1,43 +1,3 @@ -function switchToNewIssue(){ - $(".issues_content").hide("fade", { direction: "left" }, 150, function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - $("#new_issue_dialog").show("fade", { direction: "right" }, 150); - $('.top-tabs .add_new').hide(); - disableButtonIfEmptyField("#issue_title", ".save-btn"); - GitLab.GfmAutoComplete.setup(); - }); -} - -function switchToEditIssue(){ - $(".issues_content").hide("fade", { direction: "left" }, 150, function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); - $('.add_new').hide(); - disableButtonIfEmptyField("#issue_title", ".save-btn"); - GitLab.GfmAutoComplete.setup(); - }); -} - -function switchFromNewIssue(){ - backToIssues(); -} - -function switchFromEditIssue(){ - backToIssues(); -} - -function backToIssues(){ - $("#edit_issue_dialog, #new_issue_dialog").hide("fade", { direction: "right" }, 150, function(){ - $(".issues_content").show("fade", { direction: "left" }, 150, function() { - $("#edit_issue_dialog").html(""); - $("#new_issue_dialog").html(""); - $('.add_new').show(); - }); - }); -} - function initIssuesSearch() { var href = $('#issue_search_form').attr('action'); var last_terms = ''; @@ -76,23 +36,15 @@ function issuesPage(){ $(this).closest("form").submit(); }); - $("#new_issue_link").click(function(){ - updateNewIssueURL(); - }); - - $('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){ + $('body').on('ajax:success', '.close_issue, .reopen_issue', function(){ var t = $(this), totalIssues, - reopen = t.hasClass('reopen_issue'), - newIssue = false; - if( this.id == 'new_issue' ){ - newIssue = true; - } - $('.issue_counter, #new_issue').each(function(){ + reopen = t.hasClass('reopen_issue'); + $('.issue_counter').each(function(){ var issue = $(this); totalIssues = parseInt( $(this).html(), 10 ); - if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){ + if( reopen && issue.closest('.main_menu').length ){ $(this).html( totalIssues+1 ); }else { $(this).html( totalIssues-1 ); @@ -126,20 +78,3 @@ function issuesCheckChanged() { $('.issues_filters').show(); } } - -function updateNewIssueURL(){ - var new_issue_link = $("#new_issue_link"); - var milestone_id = $("#milestone_id").val(); - var assignee_id = $("#assignee_id").val(); - var new_href = ""; - if(milestone_id){ - new_href = "issue[milestone_id]=" + milestone_id + "&"; - } - if(assignee_id){ - new_href = new_href + "issue[assignee_id]=" + assignee_id; - } - if(new_href.length){ - new_href = new_issue_link.attr("href") + "?" + new_href; - new_issue_link.attr("href", new_href); - } -}; diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index 3f4b0f61dc45ce41838e341263df4aceb92e030e..f6c398c0acf17f78f3d685556220ddaceee8e983 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -7,6 +7,18 @@ window.slugify = (text) -> window.ajaxGet = (url) -> $.ajax({type: "GET", url: url, dataType: "script"}) +window.errorMessage = (message) -> + ehtml = $("<p>") + ehtml.addClass("error_message") + ehtml.html(message) + ehtml + +window.split = (val) -> + return val.split( /,\s*/ ) + +window.extractLast = (term) -> + return split( term ).pop() + # Disable button if text field is empty window.disableButtonIfEmptyField = (field_selector, button_selector) -> field = $(field_selector) @@ -33,6 +45,11 @@ $ -> # Bottom tooltip $('.has_bottom_tooltip').tooltip(placement: 'bottom') + # Flash + if (flash = $("#flash-container")).length > 0 + flash.click -> $(@).slideUp("slow") + flash.slideDown "slow" + setTimeout (-> flash.slideUp("slow")), 3000 # Disable form buttons while a form is submitting $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> diff --git a/app/assets/javascripts/merge_requests.js b/app/assets/javascripts/merge_requests.js index cc6b0771af54a66972823e9203a88bb3d504b75c..ee714f9cabb264b796ff354b816eff83a719c2a0 100644 --- a/app/assets/javascripts/merge_requests.js +++ b/app/assets/javascripts/merge_requests.js @@ -14,14 +14,6 @@ var MergeRequest = { $(".mr_show_all_commits").bind("click", function() { self.showAllCommits(); }); - - $(".line_note_link, .line_note_reply_link").live("click", function(e) { - var form = $(".per_line_form"); - $(this).parent().parent().after(form); - form.find("#note_line_code").val($(this).attr("line_code")); - form.show(); - return false; - }); }, initMergeWidget: @@ -34,6 +26,12 @@ var MergeRequest = { self.showState(data.state); }, "json"); } + + if(self.opts.ci_enable){ + $.get(self.opts.url_to_ci_check, function(data){ + self.showCiState(data.status); + }, "json"); + } }, initTabs: @@ -87,6 +85,11 @@ var MergeRequest = { $(".automerge_widget." + state).show(); }, + showCiState: + function(state){ + $(".ci_widget").hide(); + $(".ci_widget.ci-" + state).show(); + }, loadDiff: function() { diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index e536afad939916d9eb2a54cf134212fa0e6713f7..42207a390b38e7820eca9f8c8c6404f869550885 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -8,3 +8,13 @@ $ -> # Go up the hierarchy and show the corresponding submission feedback element $(@).closest('fieldset').find('.update-feedback').show('highlight', {color: '#DFF0D8'}, 500) + + $('.update-username form').on 'ajax:before', -> + $('.loading-gif').show() + $(this).find('.update-success').hide() + $(this).find('.update-failed').hide() + + $('.update-username form').on 'ajax:complete', -> + $(this).find('.save-btn').removeAttr('disabled') + $(this).find('.save-btn').removeClass('disabled') + $(this).find('.loading-gif').hide() diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee index 3059723dfa040b95ec79639698c0698b1112978f..d03a487c453c069a7abe2e150c4e0b79d4672aa2 100644 --- a/app/assets/javascripts/projects.js.coffee +++ b/app/assets/javascripts/projects.js.coffee @@ -1,8 +1,4 @@ window.Projects = -> - $('#project_name').on 'change', -> - slug = slugify $(@).val() - $('#project_code, #project_path').val slug - $('.new_project, .edit_project').on 'ajax:before', -> $('.project_new_holder, .project_edit_holder').hide() $('.save-project-loader').show() @@ -22,10 +18,3 @@ $ -> # Ref switcher $('.project-refs-select').on 'change', -> $(@).parents('form').submit() - -class @GraphNav - @init: -> - $('.graph svg').css 'position', 'relative' - $('body').bind 'keyup', (e) -> - $('.graph svg').animate(left: '+=400') if e.keyCode is 37 # left - $('.graph svg').animate(left: '-=400') if e.keyCode is 39 # right diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 3f8ed6c25522d5d83f3e81528633ba127d1dd109..5003f9b00c731b17d1172af60af7f91d9fb4e63f 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -28,7 +28,7 @@ $ -> return false $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live 'click', (e) -> - History.pushState(null, null, $(@).attr('href')) + History.pushState(null, null, decodeURIComponent($(@).attr('href'))) return false History.Adapter.bind window, 'statechange', -> diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index 424ba71f7cbe3cb29975975cce5ddd3eba754298..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,11 +0,0 @@ -/* - * This is a manifest file that'll automatically include all the stylesheets available in this directory - * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at - * the top of the compiled file, but it's generally better to create a new file per style scope. - *= require jquery.ui.all - *= require jquery.ui.aristo - *= require jquery.atwho - *= require chosen - *= require_self - *= require main -*/ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000000000000000000000000000000000000..54690e73f81d893c2d87eda4718cb290eda03969 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,47 @@ +/* + * This is a manifest file that'll automatically include all the stylesheets available in this directory + * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at + * the top of the compiled file, but it's generally better to create a new file per style scope. + *= require jquery.ui.gitlab + *= require jquery.atwho + *= require chosen + *= require_self +*/ + +/** + * GitLab bootstrap: + */ +@import "gitlab_bootstrap.scss"; + +@import "common.scss"; +@import "ref_select.scss"; + +@import "sections/header.scss"; +@import "sections/nav.scss"; +@import "sections/commits.scss"; +@import "sections/issues.scss"; +@import "sections/projects.scss"; +@import "sections/snippets.scss"; +@import "sections/votes.scss"; +@import "sections/merge_requests.scss"; +@import "sections/graph.scss"; +@import "sections/events.scss"; +@import "sections/themes.scss"; +@import "sections/tree.scss"; +@import "sections/notes.scss"; +@import "sections/profile.scss"; +@import "sections/login.scss"; +@import "sections/editor.scss"; + +@import "highlight/white.scss"; +@import "highlight/dark.scss"; + +/** + * UI themes: + */ +@import "themes/ui_basic.scss"; +@import "themes/ui_mars.scss"; +@import "themes/ui_modern.scss"; +@import "themes/ui_gray.scss"; +@import "themes/ui_color.scss"; + diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index e45cb876e7d8a41b1b81bada7fb39ea6f2bf614b..dcdfcdb289ce5fe9c7eb5407d1f709a992475537 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -1,53 +1,45 @@ /** LAYOUT **/ body { - margin-bottom:20px; + margin-bottom: 20px; } .container { - padding-top:0; - z-index:5; + padding-top: 0; + z-index: 5; } .container .content { - margin:0 0; + margin: 0 0; } -.container .sidebar { - width: 200px; - height:100%; - min-height:450px; - float:right; -} - - .visible_link, .author_link { color: $link_color; } -.help li { color:#111 } +.help li { color:$style_color; } .back_link { - text-decoration:underline; - font-size:14px; - font-weight:bold; - padding:10px 0; - padding-bottom:0; + text-decoration: underline; + font-size: 14px; + font-weight: bold; + padding: 10px 0; + padding-bottom: 0; } .info_link { - margin-right:5px; - float:left; + margin-right: 5px; + float: left; img { - width:20px; + width: 20px; } } .download_repo_link { background: url("images.png") no-repeat 0 -48px; - padding-left:20px; + padding-left: 20px; } table a code { @@ -61,32 +53,37 @@ table a code { } .loading { - margin:20px auto; + margin: 20px auto; background: url(ajax_loader.gif) no-repeat center center; - width:40px; - height:40px; + width: 40px; + height: 40px; + &.loading-gray { + background: url(ajax_loader_gray.gif) no-repeat center center; + } } /** FLASH message **/ -#flash_container { - height:50px; - position:fixed; - z-index:10001; - top:0px; - width:100%; - margin-bottom:15px; - overflow:hidden; - background:white; - cursor:pointer; - border-bottom:1px solid #ccc; +#flash-container { + height: 50px; + position: fixed; + z-index: 10001; + top: 0px; + width: 100%; + margin-bottom: 15px; + overflow: hidden; + background: white; + cursor: pointer; + border-bottom: 1px solid #ccc; + text-align: center; + display: none; h4 { - color:#666; - font-size:18px; - line-height:38px; - padding-top:5px; - margin:2px; - font-weight:normal; + color: #666; + font-size: 18px; + line-height: 38px; + padding-top: 5px; + margin: 2px; + font-weight: normal; } } @@ -94,40 +91,29 @@ table a code { margin-right:50px } -.handle:hover { - cursor:move; -} - -span.update-author { - display:block; -} span.update-author { - color:#999; - font-weight:normal; - font-style:italic; -} -span.update-author strong { - font-weight:bold; - font-style: normal; + display: block; + color: #999; + font-weight: normal; + font-style: italic; + strong { + font-weight: bold; + font-style: normal; + } } -/** UPDATE ITEM **/ -span.update-author { - display:block; -} -/** END UPDATE ITEM **/ .dashboard-loader { - float:left; - margin:10px; - display:none; + float: left; + margin: 10px; + display: none; } .user-mention { - color:#2FA0BB; - font-weight:bold; + color: #2FA0BB; + font-weight: bold; } .neib { - margin-right:10px; + margin-right: 10px; } .label { @@ -136,9 +122,9 @@ span.update-author { &.label-tag { background: none; border: none; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; + padding: 4px 6px; + color: #444; + text-shadow: 0 0 1px #fff; &.grouped { float: left; @@ -149,9 +135,9 @@ span.update-author { &.label-issue { background-color: #eee; border: 1px solid #ccc; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; + padding: 4px 6px; + color: #444; + text-shadow: 0 0 1px #fff; &.grouped { float: left; @@ -201,21 +187,21 @@ form { .field_with_errors { - display:inline; + display: inline; } ul.breadcrumb { - background:white; - border:none; + background: white; + border: none; li { display: inline; text-shadow: 0 1px 0 white } a { - color:#474D57; - font-weight:bold; - font-size:14px; + color: #474D57; + font-weight: bold; + font-size: 14px; } .arrow { @@ -225,31 +211,31 @@ ul.breadcrumb { float: left; position: relative; left: -10px; - padding:0; - margin:0; + padding: 0; + margin: 0; } } input[type=text] { &.large_text { - padding:6px; - font-size:16px; + padding: 6px; + font-size: 16px; } } input.git_clone_url { - width:325px; + width: 325px; } .merge-request-form-holder { select { - width:300px; + width: 300px; } } /** Issues **/ #issue_assignee_id { - width:300px; + width: 300px; } #new_issue_dialog textarea{ @@ -257,26 +243,11 @@ input.git_clone_url { } .project_list_url { - width:250px; + width: 250px; background:#fff !important; } -/** bordered list **/ -ul.bordered-list { - margin:5px 0px; - padding:0px; - li { - padding: 5px 0; - border-bottom: 1px solid #EEE; - overflow: hidden; - display: block; - margin:0px; - } -} - -ul.bordered-list li:last-child { border:none } - .line_holder { &:hover { td { @@ -287,10 +258,10 @@ ul.bordered-list li:last-child { border:none } li.commit { .avatar { - width:24px; + width: 24px; top:-5px; - margin-right:10px; - margin-left:10px; + margin-right: 10px; + margin-left: 10px; } code { @@ -310,102 +281,10 @@ p.time { .styled_image { - border:2px solid #ddd; -} - - -.ico { - background: url("images.png") no-repeat -85px -77px; - width: 19px; - height: 16px; - float: left; - position: relative; - margin-right: 10px; - top: 8px; - - &.project { - background-position: -37px -77px; - } - - &.activities { - background-position:-162px -22px; - } - &.projects { - background-position:-209px -21px; - } + border: 2px solid #ddd; } -.leftbar { - h5, .title { - padding:5px 10px; - } - h4 { - font-size:14px; - padding:2px 10px; - color:#666; - border-bottom:1px solid #f1f1f1; - } - a:last-child h4 { border:none; } - - a:hover { - h4 { - color:#111; - background:$hover; - border-color:#CCC; - .ico.project { - background-position:-209px -21px; - } - } - } - .bottom { - padding:10px; - } -} - -.votes { - font-size: 13px; - line-height: 15px; - .progress { - height: 4px; - margin: 0; - .bar { - float: left; - height: 100%; - } - .bar-success { - background-color: #468847; - @include bg-gradient(#62C462, #51A351); - } - .bar-danger { - background-color: #B94A48; - @include bg-gradient(#EE5F5B, #BD362F); - } - } - .upvotes { - display: inline-block; - color: #468847; - } - .downvotes { - display: inline-block; - color: #B94A48; - } -} -.votes-block { - margin: 14px 6px 6px 0; - .downvotes { - float: right; - } -} -.votes-inline { - display: inline-block; - margin: 0 8px; - .progress { - display: inline-block; - padding: 0 0 2px; - width: 45px; - } -} /* Fix for readme code (stopped it from being yellow) */ .readme { @@ -418,63 +297,45 @@ p.time { } } - .highlight_word { - background:#EEDC94; + background: #EEDC94; } .status_info { - font-size:14px; - padding:5px 15px; - line-height:24px; - width:60px; - text-align:center; - float:left; - margin-right:20px; - - &.success { - background: #5BB75B; - color: white; - text-shadow: 0 1px #111; - border-color: #9A9; - } + font-size: 14px; + padding: 5px 15px; + line-height: 26px; + text-align: center; + float: right; + position: relative; + top: -5px; + @include border-radius(4px); + &.error { background: #DA4E49; - border-color: #BD362F; - color: white; - text-shadow: 0 1px #111; + color: #FFF; } } .arrow{ background: #E3E5EA; padding: 5px; - margin-top:5px; - border-radius: 5px; + margin-top: 5px; + @include border-radius(5px); text-shadow: none; color: #999; line-height: 16px; - font-weight:bold; + font-weight: bold; } .thin_area{ height: 150px; } -.gitlab_pagination { - span a { color:$link_color; } - .prev, .next, .current, .page a { - padding:10px; - } - .current { - border-bottom:2px solid $style_color; - } -} - // Fixes alignment on notes. .new_note { label { - text-align:left; + text-align: left; } } @@ -486,7 +347,7 @@ li.note { border-bottom:none !important; } .file { - padding-left:20px; + padding-left: 20px; background:url("icon-attachment.png") no-repeat left center; } } @@ -494,7 +355,7 @@ li.note { .markdown { img { - max-width:100%; + max-width: 100%; } } @@ -504,19 +365,19 @@ li.note { .team_member_show { td:first-child { - color:#aaa; + color: #aaa; } } .remember_me { - text-align:left; + text-align: left; input { - margin:0; + margin: 0; } span { - padding-left:5px; + padding-left: 5px; } } @@ -530,10 +391,10 @@ li.note { .data { a { h1 { - line-height:48px; - font-size:48px; - padding:20px; - text-align:center; + line-height: 48px; + font-size: 48px; + padding: 20px; + text-align: center; } } } @@ -541,12 +402,12 @@ li.note { .rss-icon { img { - width:24px; - vertical-align:top; + width: 24px; + vertical-align: top; } strong { - line-height:24px; + line-height: 24px; } } @@ -554,43 +415,43 @@ li.note { /* CHZN reset few styles */ .chzn-container-single .chzn-single { - background:#FFF; + background: #FFF; border: 1px solid #bbb; - box-shadow:none; + box-shadow: none; } .chzn-container-active .chzn-single { - background:#fff; + background: #fff; } .supp_diff_link, .mr_show_all_commits { - cursor:pointer; + cursor: pointer; } .merge_request, .issue { &.today{ background: #EFE; - border-color:#CEC; + border-color: #CEC; } &.closed { background: #F5f5f5; - border-color:#E5E5E5; + border-color: #E5E5E5; } &.merged { background: #F5f5f5; - border-color:#E5E5E5; + border-color: #E5E5E5; } } .git_error_tips { @extend .span6; - text-align:left; - margin-top:40px; + text-align: left; + margin-top: 40px; pre { - background:white; - border:none; + background: white; + border: none; font-size: 12px; } } @@ -602,18 +463,22 @@ li.note { margin-bottom: 10px; background: #FEE; padding-left: 20px; + + &.centered { + text-align: center; + } } .oauth_select_holder { - padding:20px; + padding: 20px; img { - padding:5px; - margin-right:10px; + padding: 5px; + margin-right: 10px; } .active { img { - border:1px solid #ccc; - background:$hover; + border: 1px solid #ccc; + background: $hover; @include border-radius(5px); } } @@ -627,29 +492,67 @@ li.note { .gitlab-promo { a { - color:#aaa; + color: #aaa; margin-right: 30px; } } pre { &.clean { - background:none; - border:none; - margin:0; - padding:0; + background: none; + border: none; + margin: 0; + padding: 0; } } -.milestone .progress { - margin-bottom: 0; - margin-top:4px; +.milestone { + &.milestone-closed { + background: #eee; + } + .progress { + margin-bottom: 0; + margin-top: 4px; + } } .float-link { - float:left; - margin-right:15px; + float: left; + margin-right: 15px; .s16 { - margin-right:5px; + margin-right: 5px; + } +} + +.dashboard-search-filter { + padding:5px; + + .search-text-input { + float:left; + @extend .span2; + } + .btn { + margin-left: 5px; + float:left; + } +} + +h1.http_status_code { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; +} + +.control-group { + .controls { + span { + &.descr { + position: relative; + top: 2px; + left: 5px; + color: #666; + } + } } } diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss new file mode 100644 index 0000000000000000000000000000000000000000..f53e0e50bab2cb1343291e489a2dc82d3ca1f99d --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap.scss @@ -0,0 +1,26 @@ +/** Override bootstrap variables **/ +$baseFontSize: 13px !default; +$baseLineHeight: 18px !default; + +// BOOTSTRAP +@import "bootstrap"; +@import "bootstrap/responsive-utilities"; +@import "bootstrap/responsive-1200px-min"; + +@import "font-awesome"; + +/** + * GitLab bootstrap. + * Overrides some styles of twitter bootstrap. + * Also give some common classes for GitLab app + */ +@import "gitlab_bootstrap/variables.scss"; +@import "gitlab_bootstrap/fonts.scss"; +@import "gitlab_bootstrap/mixins.scss"; +@import "gitlab_bootstrap/common.scss"; +@import "gitlab_bootstrap/typography.scss"; +@import "gitlab_bootstrap/buttons.scss"; +@import "gitlab_bootstrap/blocks.scss"; +@import "gitlab_bootstrap/files.scss"; +@import "gitlab_bootstrap/tables.scss"; +@import "gitlab_bootstrap/lists.scss"; diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index ae66bd201f88eca7094e056f8a0e69f298a6bce2..f9c8b7b05ea0c54c2541aa6dc9b54b0f75c37efc 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -15,8 +15,8 @@ @extend .borders; @extend .prepend-top-20; @extend .append-bottom-20; - border-width:1px; - @include solid_shade; + border-width: 1px; + @include solid-shade; img { max-width: 100%; } @@ -30,27 +30,37 @@ .top_box_content, .middle_box_content, .bottom_box_content { - padding:15px; + padding: 15px; + word-wrap: break-word; pre { background: none !important; - margin:0; - border:none; - padding:0; + margin: 0; + border: none; + padding: 0; + } + } + + .top_box_content { + .box-title { + color: $style_color; + font-size: 18px; + font-weight: normal; + line-height: 28px; } } .middle_box_content { - border-radius:0; - border:none; - font-size:12px; - background-color:#f5f5f5; - border:none; - border-top:1px solid #eee; + @include border-radius(0); + border: none; + font-size: 12px; + background-color: #f5f5f5; + border: none; + border-top: 1px solid #eee; } .bottom_box_content { - border-top:1px solid #eee; + border-top: 1px solid #eee; } } @@ -59,44 +69,52 @@ * */ .ui-box { - background:#F9F9F9; + background: #F9F9F9; margin-bottom: 25px; - @include round-borders-all(4px); + + border: 1px solid #eaeaea; + @include border-radius(4px); + border-color: #CCC; - @include solid_shade; + @include solid-shade; &.white { - background:#fff; + background: #fff; } ul { - margin:0; + margin: 0; } h5, .title { padding: 0 10px; - @include round-borders-top(4px); + @include border-radius(4px 4px 0 0); @include bg-gray-gradient; + border-top: 1px solid #eaeaea; border-bottom: 1px solid #bbb; + > a { + text-shadow: 0 1px 1px #fff; + } + &.small { line-height: 28px; font-size: 14px; - line-height:28px; + line-height: 28px; text-shadow: 0 1px 1px white; } form { - padding:9px 0; - margin:0px; + padding: 9px 0; + margin: 0px; } .nav-pills { li { - padding:3px 0; - &.active a { background-color:$style_color; } + padding: 3px 0; + &.active a { background-color: $style_color; } a { - border-radius:7px; + @include border-radius(7px); } } } @@ -104,8 +122,8 @@ .bottom { @include bg-gray-gradient; - @include round-borders-bottom(4px); - border-bottom:none; + @include border-radius(0 0 4px 4px); + border-bottom: none; border-top: 1px solid #bbb; } @@ -116,38 +134,25 @@ padding: 5px 20px; } .middle_title { - background:#f5f5f5; + background: #f5f5f5; margin:20px -20px; padding: 0 20px; - border-top:1px solid #eee; - border-bottom:1px solid #eee; - font-size:14px; - color:#777; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + font-size: 14px; + color: #777; } } .row_title { - font-weight:bold; - color:#444; + font-weight: bold; + color: #444; &:hover { - color:#444; - text-decoration:underline; - } - } - - li, .wll { - padding:10px; - &:first-child { - @include round-borders-top(4px); - border-top:none; - } - - &:last-child { - @include round-borders-bottom(4px); - border:none; + color: #444; + text-decoration: underline; } } .ui-box-body { - padding:10px; + padding: 10px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss index f9249e871b71a7b6d232f67900f9fa2d5d90b7bf..883a87739622cc5b5ed1a938c4baa98d68f1778c 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -1,42 +1,42 @@ .btn { - @include bg-gradient(#f7f7f7, #d5d5d5); - border-color:#aaa; + @include linear-gradient(#f7f7f7, #d5d5d5); + border-color: #aaa; &:hover { @include bg-gray-gradient; - border-color:#bbb; - color:#333; + border-color: #bbb; + color: #333; } &.primary { - background:#2a79A3; - @include bg-gradient(#47A7b7, #2585b5); + background: #2a79A3; + @include linear-gradient(#47A7b7, #2585b5); border-color: #2A79A3; - color:#fff; + color: #fff; text-shadow: 0 1px 1px #268; &:hover { - background:$blue_link; - color:#fff; + background: $primary_color; + color: #fff; } &.disabled { - color:#fff; - background:#29B; + color: #fff; + background: #29B; } } &.btn-info { - background:#5aB9C3; - border-color: $blue_link; - color:#fff; + background: #5aB9C3; + border-color: $primary_color; + color: #fff; text-shadow: 0 1px 1px #268; &:hover { - background:$blue_link; - color:#fff; + background: $primary_color; + color: #fff; } &.disabled { - color:#fff; - background:#29B; + color: #fff; + background: #29B; } } @@ -49,8 +49,8 @@ } &.disabled { - color:#fff; - background:#2b2; + color: #fff; + background: #2b2; } } @@ -60,12 +60,12 @@ } &.cancel-btn { - float:right; + float: right; } &.wide { - padding-left:30px; - padding-right:30px; + padding-left: 30px; + padding-right: 30px; } &.danger { @@ -73,7 +73,7 @@ border-color: #BD362F; &:hover { - color:#fff; + color: #fff; background: #EE4E49; } } @@ -87,24 +87,24 @@ } &.active { - border-color:#aaa; - background-color:#ccc; + border-color: #aaa; + background-color: #ccc; } &.very_small { - font-size:11px; - padding:2px 6px; + font-size: 11px; + padding: 2px 6px; line-height: 16px; - margin:2px; + margin: 2px; } &.grouped { - margin-right:7px; - float:left; + margin-right: 7px; + float: left; } &.padded { - margin-right:3px; - padding:4px 10px 4px; + margin-right: 3px; + padding: 4px 10px 4px; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index c1b2880140dfe40a025615dde11c1434190faa5c..3bb7cdbf7069ae21235a5b512594c1037cfd8e79 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -10,11 +10,6 @@ /** COMMON CLASSES **/ .left { float:left } .right { float:right!important } -.width-50p { width:50% } -.width-49p { width:49% } -.width-30p { width:30% } -.width-65p { width:65% } -.width-100p { width:100% } .append-bottom-10 { margin-bottom:10px } .append-bottom-20 { margin-bottom:20px } .prepend-top-10 { margin-top:10px } @@ -24,40 +19,42 @@ .lborder { border-left:1px solid #eee } .no-padding { padding:0 !important; } .underlined { border-bottom: 1px solid #CCC; } -.no-borders { border:none; } +.no-borders { border: none; } .vlink { color: $link_color !important; } .underlined_link { text-decoration: underline; } .borders { border: 1px solid #ccc; @include shade; } .hint { font-style: italic; color: #999; } .light { color: #888 } +.tiny { font-weight: normal } /** PILLS & TABS**/ -.nav-pills a:hover { background-color:#888; } +.nav-pills a:hover { background-color: #888; } .nav-pills .active a { background-color: $style_color; } -.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; } +.nav-pills > .active > a > i[class^="icon-"] { background: inherit; } +.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; } .nav.nav-tabs { li { > a { - padding:8px 20px; + padding: 8px 20px; margin-right: 7px; line-height: 19px; border-color: #EEE; - color:#888; + color: #888; border-bottom: 1px solid #ddd; .badge { background-color: #eee; - color:#888; - text-shadow:0 1px 1px #fff; + color: #888; + text-shadow: 0 1px 1px #fff; } i[class^="icon-"] { - line-height:14px; + line-height: 14px; } } &.active { > a { border-color: #CCC; border-bottom: 1px solid #fff; - color:#333; + color: #333; } } } @@ -69,25 +66,50 @@ .alert-message.error { @extend .alert-error; } /** AVATARS **/ -img.avatar { float:left; margin-right:12px; width:40px; border:1px solid #ddd; padding:1px; } -img.avatar.s16 { width:16px; height:16px; margin-right:6px; } -img.avatar.s24 { width:24px; height:24px; margin-right:8px; } -img.avatar.s32 { width:32px; height:32px; margin-right:10px; } -img.lil_av { padding-left: 4px; padding-right:3px; } +img.avatar { float: left; margin-right: 12px; width: 40px; border: 1px solid #ddd; padding: 1px; } +img.avatar.s16 { width: 16px; height: 16px; margin-right: 6px; } +img.avatar.s24 { width: 24px; height: 24px; margin-right: 8px; } +img.avatar.s32 { width: 32px; height: 32px; margin-right: 10px; } +img.lil_av { padding-left: 4px; padding-right: 3px; } img.small { width: 80px; } /** HELPERS **/ -.nothing_here_message { text-align:center; padding:20px; color:#777; } -p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } +.nothing_here_message { + text-align: center; + padding: 20px; + color: #666; + font-weight: normal; + font-size: 16px; + line-height: 36px; +} + +p.slead { color: #456; font-size: 16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } /** FORMS **/ input[type='search'].search-text-input { background-image: url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; - padding-left:25px; + padding-left: 25px; @include border-radius(4px); - border:1px solid #ccc; + border: 1px solid #ccc; +} + +input[type='text'].danger { + background: #F2DEDE!important; + border-color: #D66; + text-shadow: 0 1px 1px #fff } fieldset legend { font-size: 17px; } + +/** PAGINATION **/ +.gitlab_pagination { + span a { color: $link_color; } + .prev, .next, .current, .page a { + padding: 10px; + } + .current { + border-bottom: 2px solid $style_color; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss index 4887d1c9402489ea4e239ef0f1fff65c8a80f474..83954da525ad0782d465f4eea1727f5108667b80 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/files.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -3,9 +3,9 @@ * */ .file_holder { - border:1px solid #BBB; - margin-bottom:1em; - @include solid_shade; + border: 1px solid #BBB; + margin-bottom: 1em; + @include solid-shade; .file_title { border-bottom: 1px solid #bbb; @@ -16,47 +16,51 @@ text-align: left; color: #666; padding: 9px 10px; - height:18px; + height: 18px; .options { - float:right; + float: right; margin-top: -5px; } .file_name { - color:$style_color; - font-size:14px; + color: $style_color; + font-size: 14px; text-shadow: 0 1px 1px #fff; small { - color:#999; - font-size:13px; + color: #999; + font-size: 13px; } } } .file_content { - background:#fff; + background: #fff; font-size: 11px; &.wiki { font-size: 13px; code { - padding:0 4px; + padding: 0 4px; } - padding:20px; - h1, h2 { - line-height: 46px; - } - h3, h4 { - line-height: 40px; + padding: 20px; + + h1 { font-size: 26px; line-height: 46px; } + h2 { font-size: 22px; line-height: 42px; } + h3 { font-size: 20px; line-height: 40px; } + h4 { font-size: 18px; line-height: 32px; } + h5 { font-size: 16px; line-height: 26px; } + + .white .highlight pre { + background: #f5f5f5; } } &.image_file { - background:#eee; - text-align:center; + background: #eee; + text-align: center; img { - padding:100px; - max-width:300px; + padding: 100px; + max-width: 300px; } } @@ -69,60 +73,60 @@ */ &.blame { table { - border:none; - box-shadow:none; - margin:0; + border: none; + box-shadow: none; + margin: 0; } tr { border-bottom: 1px solid #eee; } td { &:first-child { - border-left:none; + border-left: none; } &:last-child { - border-right:none; + border-right: none; } - background:#fff; - padding:5px; + background: #fff; + padding: 5px; } .author, .blame_commit { - background:#f5f5f5; - vertical-align:top; + background: #f5f5f5; + vertical-align: top; } .lines { pre { - padding:0; - margin:0; - background:none; - border:none; + padding: 0; + margin: 0; + background: none; + border: none; } } } &.logs { - background:#eee; + background: #eee; max-height: 700px; overflow-y: auto; ol { - margin-left:40px; + margin-left: 40px; padding: 10px 0; border-left: 1px solid #CCC; - margin-bottom:0; + margin-bottom: 0; background: white; li { - color:#888; + color: #888; p { - margin:0; - color:#333; - line-height:24px; + margin: 0; + color: #333; + line-height: 24px; padding-left: 10px; } &:hover { - background:$hover; + background: $hover; } } } @@ -142,8 +146,8 @@ table-layout: fixed; pre { - background: none; border: none; + border-radius: 0; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-size: 12px !important; line-height: 16px !important; diff --git a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss b/app/assets/stylesheets/gitlab_bootstrap/fonts.scss new file mode 100644 index 0000000000000000000000000000000000000000..88c966d18f72df8f76d91e77fb8b81adc67a0e15 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/fonts.scss @@ -0,0 +1,7 @@ +@font-face{ + font-family: Korolev; + src: font-url('korolev-medium-compressed.otf'); +} + +/** Typo **/ +$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; \ No newline at end of file diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss index 4fe45ecc2776156cefad7ffad9d73385e1e76943..edaf3cef2cf02604bf3fc757d28f153499001bfe 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -1,41 +1,75 @@ -/** LISTS **/ - -ul { - /** - * List li block element #1 - * - */ - .wll { +/** + * Well styled list + * + */ +.well-list { + margin: 0; + list-style: none; + li { background-color: #FFF; - padding: 10px 5px; + padding: 10px; min-height: 20px; border-bottom: 1px solid #eee; border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &.smoke { background-color:#f5f5f5; } + &.disabled { + color: #888; + } + + &.smoke { background-color: #f5f5f5; } + &:hover { - background:$hover; - border-bottom:1px solid #ADF; + background: $hover; + border-bottom: 1px solid #ADF; } - &:last-child { border:none } + + &:first-child { + @include border-radius(4px 4px 0 0); + border-top: none; + } + + &:last-child { + @include border-radius(0 0 4px 4px); + border: none; + } + .author { color: #999; } p { padding-top: 1px; - margin:0; - color:#222; + margin: 0; + color: #222; img { - position:relative; - top:3px; + position: relative; + top: 3px; } } + + .well-title { + font-size: 14px; + line-height: 18px; + } } } ol, ul { &.styled { li { - padding:2px; + padding: 2px; } } } + +/** light list with border-bottom between li **/ +ul.bordered-list { + margin: 5px 0px; + padding: 0px; + li { + padding: 5px 0; + border-bottom: 1px solid #EEE; + overflow: hidden; + display: block; + margin: 0px; + &:last-child { border:none } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss new file mode 100644 index 0000000000000000000000000000000000000000..81830368c4cd2e33f9c9f279647197c8cb2d993b --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/mixins.scss @@ -0,0 +1,69 @@ +/** + * Generic mixins + */ + @mixin box-shadow($shadow) { + -webkit-box-shadow: $shadow; + -moz-box-shadow: $shadow; + -ms-box-shadow: $shadow; + -o-box-shadow: $shadow; + box-shadow: $shadow; +} + +@mixin border-radius($radius) { + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + -ms-border-radius: $radius; + -o-border-radius: $radius; + border-radius: $radius; +} + +@mixin linear-gradient($from, $to) { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); + background-image: -webkit-linear-gradient($from, $to); + background-image: -moz-linear-gradient($from, $to); + background-image: -o-linear-gradient($from, $to); +} + +/** + * Prefilled mixins + * Mixins with fixed values + */ +@mixin bg-light-gray-gradient { + background: #f1f1f1; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1)); + background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1); + background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1); + background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1); +} + +@mixin bg-gray-gradient { + background: #eee; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +} + +@mixin bg-dark-gray-gradient { + background: #eee; + background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); +} + +@mixin shade { + @include box-shadow(0 0 3px #ddd); +} + +@mixin solid-shade { + @include box-shadow(0 0 0 3px #f1f1f1); +} + +@mixin header-font { + color: $style_color; + text-shadow: 0 1px 1px #FFF; + font-family: 'Korolev', sans-serif; + font-size: 28px; + line-height: 48px; + font-weight: normal; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss index 549cdfee5a67084ba56c76cb933b2ab8d560b34a..5905efd3aae68a2976012a5faf29392e6bf9ecfb 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/tables.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss @@ -1,13 +1,13 @@ table { @extend .table; @extend .table-striped; - @include solid_shade; - border:1px solid #bbb; - width:100%; + @include solid-shade; + border: 1px solid #bbb; + width: 100%; &.low { td { - line-height:18px; + line-height: 18px; } } @@ -31,8 +31,8 @@ table { } td { - border-color:#f1f1f1; - line-height:28px; + border-color: #f1f1f1; + line-height: 28px; .s16 { margin-top: 5px; @@ -40,11 +40,11 @@ table { } &:first-child { - border-left:1px solid #bbb; + border-left: 1px solid #bbb; } &:last-child { - border-right:1px solid #bbb; + border-right: 1px solid #bbb; } } @@ -53,10 +53,10 @@ table { } &.lite { - border:none; - box-shadow:none; + border: none; + box-shadow: none; tr, td { - border:none; + border: none; background:none !important; } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss index fe3bd68b608f6887a4ce7ef6b19ac409d009a2b1..81fb79a43f2769bfda860d56908a93ed517a7b5d 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -5,11 +5,11 @@ h1, h2, h3, h4, h5, h6 { margin: 0; } h3, h4, h5, h6 { line-height: 36px; } -h5 { font-size:14px; } +h5 { font-size: 14px; } h3.page_title { - color:#456; - font-size:20px; + color: #456; + font-size: 20px; font-weight: normal; line-height: 28px; } @@ -25,7 +25,7 @@ pre { &.dark { background: #333; - color:#f5f5f5; + color: #f5f5f5; } } @@ -37,8 +37,8 @@ a { outline: none; color: $link_color; &:hover { - text-decoration:none; - color: $blue_link; + text-decoration: none; + color: $primary_color; } &.btn { @@ -53,27 +53,31 @@ a { } &.lined { - text-decoration:underline; - &:hover { text-decoration:underline; } + text-decoration: underline; + &:hover { text-decoration: underline; } } &.gray { - color:gray; + color: gray; } &.supp_diff_link { - text-align:center; - padding:20px 0; - background:#f1f1f1; - width:100%; - float:left; + text-align: center; + padding: 20px 0; + background: #f1f1f1; + width: 100%; + float: left; } &.neib { - margin-right:15px; + margin-right: 15px; } } a:focus { outline: none; } + +.monospace { + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/variables.scss b/app/assets/stylesheets/gitlab_bootstrap/variables.scss new file mode 100644 index 0000000000000000000000000000000000000000..869eb168c0dadfb02e1eeb1e09703cae3310cffb --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/variables.scss @@ -0,0 +1,5 @@ +/** Colors **/ +$primary_color: #2FA0BB; +$link_color: #3A89A3; +$style_color: #474D57; +$hover: #D9EDF7; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 0996cc772f4523cc0a019a4f8a7ec836765ee9ad..6018ff7074d3340d80d164edc6c4679cbdd79474 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,6 +1,9 @@ -.black .lines .highlight { - background: #333; - pre { color: #eee; } +.black .highlight { + background-color: #333; + pre { + color: #eee; + background: inherit; + } .hll { display: block; background-color: darken($hover, 65%) } .c { color: #888888; font-style: italic } /* Comment */ @@ -21,43 +24,43 @@ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #606060 } /* Generic.Subheading */ .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight:bold;} /* Keyword.Constant */ - .kd{font-weight:bold;} /* Keyword.Declaration */ - .kn{font-weight:bold;} /* Keyword.Namespace */ - .kp{font-weight:bold;} /* Keyword.Pseudo */ - .kr{font-weight:bold;} /* Keyword.Reserved */ - .kt{color:#458;font-weight:bold;} /* Keyword.Type */ + .kc{font-weight: bold;} /* Keyword.Constant */ + .kd{font-weight: bold;} /* Keyword.Declaration */ + .kn{font-weight: bold;} /* Keyword.Namespace */ + .kp{font-weight: bold;} /* Keyword.Pseudo */ + .kr{font-weight: bold;} /* Keyword.Reserved */ + .kt{color: #458;font-weight: bold;} /* Keyword.Type */ .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .p { color: #eee; } .s { color: #0AD; background-color: transparent } /* Literal.String */ - .na{color:#008080;} /* Name.Attribute */ - .nb{color:#0086B3;} /* Name.Builtin */ - .nc{color:#ccc;font-weight:bold;} /* Name.Class */ - .no{color:turquoise;} /* Name.Constant */ - .ni{color:#800080;} - .ne{color:#900;font-weight:bold;} /* Name.Exception */ - .nf{color:#ccc;font-weight:bold;} /* Name.Function */ - .nn{color:#79C3E0;font-weight:bold;} /* Name.Namespace */ - .nt{color:#fc5;} /* Name.Tag */ - .nv{color:#FA4;} /* Name.Variable */ + .na{color: #008080;} /* Name.Attribute */ + .nb{color: #0086B3;} /* Name.Builtin */ + .nc{color: #ccc;font-weight: bold;} /* Name.Class */ + .no{color: turquoise;} /* Name.Constant */ + .ni{color: #800080;} + .ne{color: #900;font-weight: bold;} /* Name.Exception */ + .nf{color: #ccc;font-weight: bold;} /* Name.Function */ + .nn{color: #79C3E0;font-weight: bold;} /* Name.Namespace */ + .nt{color: #fc5;} /* Name.Tag */ + .nv{color: #FA4;} /* Name.Variable */ .py { color: #336699; font-weight: bold } /* Name.Property */ .ow { color: #008800 } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ .mf { color: #7AC; font-weight: bold } /* Literal.Number.Float */ .mh { color: #7AC; font-weight: bold } /* Literal.Number.Hex */ - .mi {color:#099;} /* Literal.Number.Integer */ + .mi {color: #099;} /* Literal.Number.Integer */ .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .sb { color: #dd2200; background-color: transparent; } /* Literal.String.Backtick */ - .sc{color:#d14;} /* Literal.String.Char */ + .sc{color: #d14;} /* Literal.String.Char */ .sd { color: #dd2200; background-color: transparent; } /* Literal.String.Doc */ - .s2{color:orange;} /* Literal.String.Double */ - .se{color:orange;} /* Literal.String.Escape */ - .sh{color:orange;} /* Literal.String.Heredoc */ - .si{color:orange;} /* Literal.String.Interpol */ - .sx{color:orange;} /* Literal.String.Other */ - .sr{color:orange;} /* Literal.String.Regex */ - .s1{color:orange;} /* Literal.String.Single */ - .ss{color:orange;} /* Literal.String.Symbol */ + .s2{color: orange;} /* Literal.String.Double */ + .se{color: orange;} /* Literal.String.Escape */ + .sh{color: orange;} /* Literal.String.Heredoc */ + .si{color: orange;} /* Literal.String.Interpol */ + .sx{color: orange;} /* Literal.String.Other */ + .sr{color: orange;} /* Literal.String.Regex */ + .s1{color: orange;} /* Literal.String.Single */ + .ss{color: orange;} /* Literal.String.Symbol */ .bp { color: #D58 } /* Name.Builtin.Pseudo */ .vc { color: #336699 } /* Name.Variable.Class */ .vg { color: #dd7700 } /* Name.Variable.Global */ diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index d6792b37e9cbbfb852f0b2197aa8f46ce6399177..f200e1d7b6056ed54b8d6ff7cb09af8f3ef07881 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,6 +1,8 @@ -.white .lines .highlight { - background: white; - pre { color: #333; } +.white .highlight { + pre { + background-color: #fff; + color: #333; + } .hll { display: block; background-color: $hover } .c { color: #888888; font-style: italic } /* Comment */ @@ -20,42 +22,42 @@ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #606060 } /* Generic.Subheading */ .gt { color: #aa0000 } /* Generic.Traceback */ - .kc{font-weight:bold;} /* Keyword.Constant */ - .kd{font-weight:bold;} /* Keyword.Declaration */ - .kn{font-weight:bold;} /* Keyword.Namespace */ - .kp{font-weight:bold;} /* Keyword.Pseudo */ - .kr{font-weight:bold;} /* Keyword.Reserved */ - .kt{color:#458;font-weight:bold;} /* Keyword.Type */ + .kc{font-weight: bold;} /* Keyword.Constant */ + .kd{font-weight: bold;} /* Keyword.Declaration */ + .kn{font-weight: bold;} /* Keyword.Namespace */ + .kp{font-weight: bold;} /* Keyword.Pseudo */ + .kr{font-weight: bold;} /* Keyword.Reserved */ + .kt{color: #458;font-weight: bold;} /* Keyword.Type */ .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ - .na{color:#008080;} /* Name.Attribute */ - .nb{color:#0086B3;} /* Name.Builtin */ - .nc{color:#458;font-weight:bold;} /* Name.Class */ - .no{color:#008080;} /* Name.Constant */ - .ni{color:#800080;} - .ne{color:#900;font-weight:bold;} /* Name.Exception */ - .nf{color:#900;font-weight:bold;} /* Name.Function */ - .nn{color:#005;font-weight:bold;} /* Name.Namespace */ - .nt{color:#000080;} /* Name.Tag */ - .nv{color:#008080;} /* Name.Variable */ + .na{color: #008080;} /* Name.Attribute */ + .nb{color: #0086B3;} /* Name.Builtin */ + .nc{color: #458;font-weight: bold;} /* Name.Class */ + .no{color: #008080;} /* Name.Constant */ + .ni{color: #800080;} + .ne{color: #900;font-weight: bold;} /* Name.Exception */ + .nf{color: #900;font-weight: bold;} /* Name.Function */ + .nn{color: #005;font-weight: bold;} /* Name.Namespace */ + .nt{color: #000080;} /* Name.Tag */ + .nv{color: #008080;} /* Name.Variable */ .py { color: #336699; font-weight: bold } /* Name.Property */ .ow { color: #008800 } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ - .mi {color:#099;} /* Literal.Number.Integer */ + .mi {color: #099;} /* Literal.Number.Integer */ .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ - .sc{color:#d14;} /* Literal.String.Char */ + .sc{color: #d14;} /* Literal.String.Char */ .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ - .s2{color:#d14;} /* Literal.String.Double */ - .se{color:#d14;} /* Literal.String.Escape */ - .sh{color:#d14;} /* Literal.String.Heredoc */ - .si{color:#d14;} /* Literal.String.Interpol */ - .sx{color:#d14;} /* Literal.String.Other */ - .sr{color:#d14;} /* Literal.String.Regex */ - .s1{color:#d14;} /* Literal.String.Single */ - .ss{color:#d14;} /* Literal.String.Symbol */ + .s2{color: #d14;} /* Literal.String.Double */ + .se{color: #d14;} /* Literal.String.Escape */ + .sh{color: #d14;} /* Literal.String.Heredoc */ + .si{color: #d14;} /* Literal.String.Interpol */ + .sx{color: #d14;} /* Literal.String.Other */ + .sr{color: #d14;} /* Literal.String.Regex */ + .s1{color: #d14;} /* Literal.String.Single */ + .ss{color: #d14;} /* Literal.String.Symbol */ .bp { color: #003388 } /* Name.Builtin.Pseudo */ .vc { color: #336699 } /* Name.Variable.Class */ .vg { color: #dd7700 } /* Name.Variable.Global */ @@ -63,7 +65,5 @@ } .shadow { - -webkit-box-shadow:0 5px 15px #000; - -moz-box-shadow:0 5px 15px #000; - box-shadow:0 5px 15px #000; + @include box-shadow(0 5px 15px #000); } diff --git a/app/assets/stylesheets/jquery.ui.gitlab.css b/app/assets/stylesheets/jquery.ui.gitlab.css new file mode 100644 index 0000000000000000000000000000000000000000..5c51600ba6783daf6a3b9dd37fd38d3992b515a0 --- /dev/null +++ b/app/assets/stylesheets/jquery.ui.gitlab.css @@ -0,0 +1,257 @@ +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.7 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #CCC; background: #ffffff; color: #4F4F4F; } +.ui-widget-content a { color: #4F4F4F; } +.ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; } +.ui-widget-header { + background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */ + background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */ + background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */ +} +.ui-widget-header a { color: #4F4F4F; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #B6B6B6; font-weight: normal; color: #4F4F4F; } +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { + background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */ + background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */ + background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */ + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; + box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; +} +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #9D9D9D; font-weight: normal; color: #313131; } +.ui-state-hover a, .ui-state-hover a:hover { color: #313131; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { + outline: none; + color: #1c4257; border: 1px solid #7096ab; + background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */ + background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */ + background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */ + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { border: 1px solid #d2dbf4; background: #f4f8fd; color: #0d2054; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { border: 1px solid #e2d0d0; background: #fcf0f0; color: #280b0b; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(ui-icons_222222_256x240.png); } +.ui-state-default .ui-icon { background-image: url(ui-icons_454545_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(ui-icons_454545_256x240.png); } +.ui-state-active .ui-icon {background-image: url(ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(ui-icons_454545_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { background: url(icon_sprite.png) -16px 0 no-repeat !important; } +.ui-state-highlight .ui-icon, .ui-state-error .ui-icon { margin-top: -1px; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #000000; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; } +/* + * jQuery UI Selectable 1.8.7 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Autocomplete 1.8.7 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { + position: absolute; cursor: default; z-index: 3; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3); + -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3); + box-shadow: 0 1px 5px rgba(0,0,0,0.3); +} + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.7 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 1px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; + color: #666; + font-size: 13px; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; + background: #D9EDF7; + color: #3A89A3; + text-shadow: 0px 1px 1px #fff; + border: none; + border: 1px solid #ADE; + cursor: pointer; + font-weight: bold; +} + +/* + * jQuery UI Datepicker 1.8.7 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { + width: 17em; + padding: 0; + display: none; + border-color: #DDDDDD; + border: none; + box-shadow: none; +} +.ui-datepicker .ui-datepicker-header { + position:relative; + padding:.35em 0; + border: none; + border-bottom: 1px solid #B6B6B6; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + margin-bottom: 10px; + border: 1px solid #bbb; + -webkit-box-shadow: 0 0 0 3px #F1F1F1; + -moz-box-shadow: 0 0 0 3px #f1f1f1; + -ms-box-shadow: 0 0 0 3px #f1f1f1; + -o-box-shadow: 0 0 0 3px #f1f1f1; + box-shadow: 0 0 0 3px #F1F1F1; +} + +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 6px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { border: 1px none; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev span { background-position: 0px -32px !important; } +.ui-datepicker .ui-datepicker-next span { background-position: -16px -32px !important; } +.ui-datepicker .ui-datepicker-prev-hover span { background-position: 0px -48px !important; } +.ui-datepicker .ui-datepicker-next-hover span { background-position: -16px -48px !important; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; background: url(icon_sprite.png) no-repeat; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; font-size: 12px; text-shadow: 0 1px 0 rgba(255,255,255,0.6); } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; line-height: 24px; background-color: #FFF!important; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } +.ui-datepicker table .ui-state-highlight { border-color: #ADE; } +.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; } +.ui-datepicker-calendar .ui-state-active { background: #D9EDF7; border-color: #ADE; color: #3A89A3; font-weight: bold; text-shadow: 0 1px 1px #fff; } diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss deleted file mode 100644 index 7ae32b3a7d9380e348cea6b2d1b2c9c7f3116b8c..0000000000000000000000000000000000000000 --- a/app/assets/stylesheets/main.scss +++ /dev/null @@ -1,204 +0,0 @@ -/** Override bootstrap variables **/ -$baseFontSize: 13px !default; -$baseLineHeight: 18px !default; - -@import "bootstrap"; -@import "bootstrap-responsive"; -@import 'font-awesome'; - -/** GitLab colors **/ -$link_color: #3A89A3; -$blue_link: #2FA0BB; -$style_color: #474D57; -$hover: #D9EDF7; -$hover_border: #ADF; - -/** GitLab Fonts **/ -@font-face { font-family: Korolev; src: font-url('korolev-medium-compressed.otf'); } -$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; - -/** MIXINS **/ -@mixin shade { - -moz-box-shadow: 0 0 3px #ddd; - -webkit-box-shadow: 0 0 3px #ddd; - box-shadow: 0 0 3px #ddd; -} - -@mixin solid_shade { - -moz-box-shadow: 0 0 0 3px #f1f1f1; - -webkit-box-shadow: 0 0 0 3px #f1f1f1; - box-shadow: 0 0 0 3px #f1f1f1; -} - -@mixin border-radius($radius) { - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - border-radius: $radius; -} - -@mixin round-borders-bottom($radius) { - border-top: 1px solid #eaeaea; - -moz-border-radius-bottomright: $radius; - -moz-border-radius-bottomleft: $radius; - border-bottom-right-radius: $radius; - border-bottom-left-radius: $radius; - -webkit-border-bottom-left-radius: $radius; - -webkit-border-bottom-right-radius: $radius; -} - -@mixin round-borders-top($radius) { - border-top: 1px solid #eaeaea; - -moz-border-radius-topright: $radius; - -moz-border-radius-topleft: $radius; - border-top-right-radius: $radius; - border-top-left-radius: $radius; - -webkit-border-top-left-radius: $radius; - -webkit-border-top-right-radius: $radius; -} - -@mixin round-borders-all($radius) { - border: 1px solid #eaeaea; - -moz-border-radius: $radius; - -webkit-border-radius: $radius; - border-radius: $radius; -} - -@mixin bg-gradient($from, $to) { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); - background-image: -webkit-linear-gradient($from, $to); - background-image: -moz-linear-gradient($from, $to); - background-image: -o-linear-gradient($from, $to); -} - -@mixin bg-light-gray-gradient { - background:#f1f1f1; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1)); - background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1); - background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1); - background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1); -} - -@mixin bg-gray-gradient { - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); -} - -@mixin bg-dark-gray-gradient { - background:#eee; - background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); - background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); - background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); -} - -/** - * Header of application. - * Contain application logo, search panel, profile icon - */ -@import "sections/header.scss"; - -/** - * Navigation menu of application. - * Panel with links to pages depends on project, profile or admin area - */ -@import "sections/nav.scss"; - -/** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link, button colors - * - header restyles - * - main menu restyles - * - */ -@import "themes/ui_basic.scss"; - -/** - * UI themes: - */ -@import "themes/ui_mars.scss"; -@import "themes/ui_modern.scss"; -@import "themes/ui_gray.scss"; -@import "themes/ui_color.scss"; - -/** - * GitLab bootstrap. - * Overrides some styles of twitter bootstrap. - * Also give some common classes for GitLab app - */ -@import "gitlab_bootstrap/common.scss"; -@import "gitlab_bootstrap/typography.scss"; -@import "gitlab_bootstrap/buttons.scss"; -@import "gitlab_bootstrap/blocks.scss"; -@import "gitlab_bootstrap/files.scss"; -@import "gitlab_bootstrap/tables.scss"; -@import "gitlab_bootstrap/lists.scss"; - - -/** - * Most of application styles placed here. - * This file represent common UI that should not be changed between themes - * or project restyling like form width or user avatar class or commit title - * - * TODO: clean it - */ -@import "common.scss"; - -/** - * Styles related to specific part of app - */ -@import "sections/commits.scss"; -@import "sections/issues.scss"; -@import "sections/projects.scss"; -@import "sections/merge_requests.scss"; -@import "sections/graph.scss"; -@import "sections/events.scss"; -@import "sections/themes.scss"; - -/** - * This scss file redefine chozen selectbox styles for - * project Branch/Tag select element - */ -@import "ref_select.scss"; - -/** - * Code (files list) styles. Browsing project files there - */ -@import "sections/tree.scss"; - -/** - * This file represent notes(comments) styles - */ -@import "sections/notes.scss"; - -/** - * This file represent profile styles - */ -@import "sections/profile.scss"; - -/** - * Devise styles - */ -@import "sections/login.scss"; - -/** - * CODE HIGHTLIGHT BASE - * - */ -@import "highlight/white.scss"; - -/** - * CODE HIGHTLIGHT DARK schema - * - */ -@import "highlight/dark.scss"; - -/** - * File Editor styles - * - */ -@import "sections/editor.scss"; diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 377d008605e409c658f83dc4c5c9defe2a872a1f..284d1c324e5f2ac4381931be6515a85de0a4fec9 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -1,6 +1,6 @@ /** Branch/tag selector **/ .project-refs-form { - margin:0; + margin: 0; span { background:none !important; position:static !important; @@ -9,7 +9,7 @@ } } .project-refs-select { - width:120px; + width: 120px; } .project-refs-form .chzn-container { @@ -21,10 +21,10 @@ .chzn-drop { min-width: 400px; .chzn-results { - max-height:300px; + max-height: 300px; } .chzn-search input { - min-width:365px; + min-width: 365px; } } } @@ -33,21 +33,19 @@ .chzn-container { .chzn-search { input:focus { - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; + @include box-shadow(none); } } .chzn-drop { - margin:7px 0; + margin: 7px 0; min-width: 200px; border: 1px solid #bbb; - border-radius:0; + @include border-radius(0); .chzn-results { margin-top: 5px; - max-height:300px; + max-height: 300px; .group-result { color: $style_color; @@ -55,7 +53,7 @@ padding: 8px; } .active-result { - border-radius: 0; + @include border-radius(0); &.highlighted { background: $hover; @@ -71,7 +69,7 @@ .chzn-search { @include bg-gray-gradient; input { - min-width:165px; + min-width: 165px; border-color: #CCC; } } @@ -81,8 +79,8 @@ @include bg-light-gray-gradient; div { - background:transparent; - border-left:none; + background: transparent; + border-left: none; } span { diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index 9cce5bd0389f38d9f54ffb5c33c5ce5567a3ba93..7ed53333f8cbce5cb0396250a2b3f49681a1f0c5 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -6,14 +6,14 @@ .commit-title { line-height: 26px; - margin:0; + margin: 0; } .commit-description { font-size: 14px; border: none; background-color: white; - padding-top:10px; + padding-top: 10px; } .browse-button { @@ -28,9 +28,9 @@ @extend .clearfix; .sha-block { - text-align:right; + text-align: right; &:first-child { - padding-bottom:6px; + padding-bottom: 6px; } a { @@ -49,10 +49,10 @@ .author a, .committer a { - font-size:14px; - line-height:22px; - text-shadow:0 1px 1px #fff; - color:#777; + font-size: 14px; + line-height: 22px; + text-shadow: 0 1px 1px #fff; + color: #777; &:hover { color: #999; } @@ -70,15 +70,16 @@ * */ .diff_file { - border:1px solid #CCC; - margin-bottom:1em; + border: 1px solid #CCC; + margin-bottom: 1em; .diff_file_header { @extend .clearfix; padding: 5px 5px 5px 10px; color: #555; - border-bottom:1px solid #CCC; + border-bottom: 1px solid #CCC; background: #eee; + // TODO Replace with linear-gradient mixin background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); @@ -86,7 +87,7 @@ > span { font-family: $monospace; - font-size:14px; + font-size: 14px; line-height: 30px; } @@ -104,36 +105,36 @@ } } .diff_file_content { - overflow:auto; - overflow-y:hidden; - background:#fff; - color:#333; + overflow: auto; + overflow-y: hidden; + background: #fff; + color: #333; font-size: 12px; font-family: $monospace; .old{ span.idiff{ - background-color:#FAA; + background-color: #FAA; } } .new{ span.idiff{ - background-color:#AFA; + background-color: #AFA; } } table { td { - line-height:18px; + line-height: 18px; } } } .diff_file_content_image { - background:#eee; - text-align:center; + background: #eee; + text-align: center; .image { display: inline-block; - margin:50px; - max-width:400px; + margin: 50px; + max-width: 400px; img{ background: url('trans_bg.gif'); @@ -158,7 +159,7 @@ &.img_compared { .image { - max-width:300px; + max-width: 300px; } } } @@ -166,46 +167,46 @@ .diff_file_content{ table { - border:none; - margin:0px; - padding:0px; + border: none; + margin: 0px; + padding: 0px; tr { td { - font-size:12px; + font-size: 12px; } } } .old_line, .new_line { - margin:0px; - padding:0px; - border:none; - background:#EEE; - color:#666; + margin: 0px; + padding: 0px; + border: none; + background: #EEE; + color: #666; padding: 0px 5px; border-right: 1px solid #ccc; - text-align:right; - min-width:35px; - max-width:35px; - width:35px; + text-align: right; + min-width: 35px; + max-width: 35px; + width: 35px; moz-user-select: none; -khtml-user-select: none; user-select: none; a { - float:left; - width:35px; - font-weight:normal; - color:#666; + float: left; + width: 35px; + font-weight: normal; + color: #666; &:hover { - text-decoration:underline; + text-decoration: underline; } } } .line_content { - white-space:pre; - height:14px; - margin:0px; - padding:0px; - border:none; + white-space: pre; + height: 14px; + margin: 0px; + padding: 0px; + border: none; &.new { background: #CFD; } @@ -213,8 +214,8 @@ background: #FDD; } &.matched { - color:#ccc; - background:#fafafa; + color: #ccc; + background: #fafafa; } } } @@ -228,32 +229,30 @@ /** COMMIT ROW **/ .commit { - @extend .wll; - .browse_code_link_holder { @extend .span2; - float:right; + float: right; } .committed_ago { - float:right; + float: right; @extend .cgray; } .notes_count { - float:right; + float: right; margin: -6px 8px 6px; } code { - background:#FCEEC1; - color:$style_color; + background: #FCEEC1; + color: $style_color; } .commit_short_id { - float:left; + float: left; @extend .lined; - min-width:65px; + min-width: 65px; font-family: $monospace; } @@ -294,11 +293,24 @@ } .label_commit { - @include round-borders-all(4px); - padding:2px 4px; - border:none; - font-size:13px; + @include border-radius(4px); + padding: 2px 4px; + font-size: 13px; background: #474D57; - color:#fff; + color: #fff; font-family: $monospace; } + + +.commits-compare-switch{ + background: url("switch_icon.png") no-repeat center center; + width: 16px; + height: 18px; + text-indent: -9999px; + float: left; + margin-right: 9px; + border: 1px solid #DDD; + @include border-radius(4px); + padding: 4px; + background-color: #EEE; +} diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/sections/editor.scss index 711dc0d8e43cb07550e3e2333bdc7c05d9daa05b..a71e5438936958f9a67ecf50d674b4b404f4dadd 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/sections/editor.scss @@ -1,7 +1,7 @@ .file-editor { #editor{ border: none; - border-radius: 0; + @include border-radius(0); height: 500px; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 369ebc81e31abbfef432c15ba96e2f214a9a43aa..071a9c35468225f4ccb766dfd39f210bc3784858 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -4,25 +4,25 @@ */ .event_label { &.pushed { - padding:0 2px; + padding: 0 2px; } &.opened { - padding:0 2px; + padding: 0 2px; } &.closed { - padding:0 2px; + padding: 0 2px; } &.merged { - padding:0 2px; + padding: 0 2px; } &.left, &.joined { - padding:0 2px; - float:none; + padding: 0 2px; + float: none; } } @@ -31,49 +31,51 @@ * */ .event-item { - min-height:40px; - border-bottom:1px solid #eee; + border-bottom: 1px solid #eee; .event-title { - color:#333; + color: #333; font-weight: bold; .author_name { - color:#333; + color: #333; } } .event-body { p { - color:#555; + color: #555; padding-top: 5px; } .event-info { - color:#666; + color: #666; } } .avatar { - width:32px; + position: relative; + top: -3px; } .event_icon { + position: relative; float: right; border: 1px solid #EEE; padding: 5px; @include border-radius(5px); background: #F9F9F9; + margin-left: 10px; + top: -6px; img { - width:20px; + width: 20px; } } ul { - margin-left:50px; - margin-bottom:5px; + margin-left: 50px; + margin-bottom: 5px; .avatar { - width:18px; - margin-top:3px; + width: 18px; + margin-top: 3px; } } - padding: 15px 5px; + padding: 16px 5px; &:last-child { border:none } - .wll:hover { background:none } .event_commits { margin-top: 5px; @@ -81,9 +83,9 @@ li { &.commit { background: transparent; - padding:3px; - border:none; - font-size:12px; + padding: 3px; + border: none; + font-size: 12px; } &.commits-stat { display: block; @@ -98,15 +100,15 @@ * */ .event_lp { - color:#777; - padding:10px; - min-height:22px; + color: #777; + padding: 10px; + min-height: 22px; border-left: 5px solid #5AB9C3; - margin-bottom:20px; - background:#f9f9f9; + margin-bottom: 20px; + background: #f9f9f9; .avatar { - width:24px; + width: 24px; } .btn-new-mr { @@ -133,7 +135,7 @@ background: #f9f9f9; margin-bottom: 10px; img { - width:20px; + width: 20px; } &.inactive { diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 2aa4463e45e0255db488f9e46edfee582412de44..5800098ade48066e5097eaed5d3addfc853d2432 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -1,10 +1,10 @@ .graph_holder { border: 1px solid #aaa; - padding:1px; + padding: 1px; h4 { - padding:0 10px; + padding: 0 10px; border-bottom: 1px solid #bbb; @include bg-gray-gradient; } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index 0db40ec9ac7504527a9c12e010b0ea17c6844d4f..c1b210be1ab5a7dc639fc5b0f4d7fce290e20ee6 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -5,7 +5,7 @@ header { &.navbar-gitlab { .navbar-inner { - height:45px; + height: 45px; padding: 5px; background: #F1F1F1; @@ -24,8 +24,8 @@ header { } } - z-index:10; - /*height:60px;*/ + z-index: 10; + /*height: 60px;*/ /** * @@ -33,25 +33,20 @@ header { * */ .app_logo { - width:170px; - float:left; + width: 170px; + float: left; a { - float:left; + float: left; padding: 0px; h1 { - width:90px; + width: 90px; background: url('logo_dark.png') no-repeat 0px 2px; - float:left; - margin-left:2px; - font-size:30px; - line-height:48px; - font-weight:normal; - color:$style_color; - text-shadow: 0 1px 1px #FFF; - padding-left:45px; - height:40px; - font-family: 'Korolev', sans-serif; + float: left; + margin-left: 2px; + padding-left: 45px; + height: 40px; + @include header-font; } } } @@ -62,16 +57,11 @@ header { * */ .project_name { - position:relative; - float:left; - margin:0; - margin-right:30px; - font-size:30px; - line-height:48px; - font-weight:normal; - color:$style_color; - text-shadow: 0 1px 1px #FFF; - font-family: 'Korolev', sans-serif; + position: relative; + float: left; + margin: 0; + margin-right: 30px; + @include header-font; } /** @@ -81,7 +71,7 @@ header { */ .search { margin-right: 45px; - margin-left:10px; + margin-left: 10px; margin-top: 2px; .search-input { @@ -89,11 +79,11 @@ header { background-image: url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; - padding-left:25px; + padding-left: 25px; font-size: 13px; @include border-radius(3px); - border:1px solid #c6c6c6; - box-shadow:none; + border: 1px solid #c6c6c6; + box-shadow: none; &:focus { @extend .span3; } @@ -122,7 +112,7 @@ header { width: 28px; height: 28px; display: block; - top:1px; + top: 1px; &:after { content: " "; display: block; @@ -132,12 +122,15 @@ header { left: 0; bottom: 0; float: right; - border-radius: 5px; + @include border-radius(5px); border: 1px solid rgba(255, 255, 255, 0.1); border-bottom: 0; - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255, 255, 255, 0.15)), to(rgba(0, 0, 0, 0.25))), -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(255, 255, 255, 0)), color-stop(0.5, rgba(255, 255, 255, 0.1)), color-stop(0.501, rgba(255, 255, 255, 0)), color-stop(1, rgba(255, 255, 255, 0))); - background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), -moz-linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); - background: linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255, 255, 255, 0.15)), to(rgba(0, 0, 0, 0.25))), + -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(255, 255, 255, 0)), color-stop(0.5, rgba(255, 255, 255, 0.1)), color-stop(0.501, rgba(255, 255, 255, 0)), color-stop(1, rgba(255, 255, 255, 0))); + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), + -moz-linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); + background: linear-gradient(top, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.25)), + linear-gradient(left top, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0)); -webkit-background-origin: border-box; -moz-background-origin: border; background-origin: border-box; } } } @@ -149,7 +142,7 @@ header { display: block; } } .account-links { - border-radius: 5px; + @include border-radius(5px); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); position: relative; &:before { @@ -169,7 +162,7 @@ header { display: none; z-index: 100000; @include border-radius(4px); - width: 100px; + width: 130px; position: absolute; right: 5px; top: 38px; @@ -178,13 +171,13 @@ header { box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); a { color: #fff; - padding: 7px 10px; + padding: 12px 15px; display: block; text-shadow: none; border-bottom: 1px solid #666; font-size: 12px; &:hover { - color:#fff; + color: #fff; background: #333; } } @@ -197,20 +190,13 @@ header { .account-links a { &:first-child { - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; } + @include border-radius(5px 5px 0 0); + } &:last-child { - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; - border-bottom: 0; } } + @include border-radius(0 0 5px 5px); + border-bottom: 0; + } + } @@ -248,13 +234,13 @@ header { a { h1 { background: url('logo_white.png') no-repeat 0px 2px; - color:#fff; + color: #fff; text-shadow: 0 1px 1px #111; } } } .project_name { - color:#fff; + color: #fff; text-shadow: 0 1px 1px #111; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 93622d6149ab95da4291bcd6b05460b79d980618..fd995728978c18237d618c4f9bf5f3ba5acdef74 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -3,7 +3,7 @@ .issue_title { @extend .top_box_content; .clearfix { - margin-bottom:0px; + margin-bottom: 0px; input { @extend .span8; } @@ -11,14 +11,14 @@ } .issue_middle_block { @extend .middle_box_content; - height:30px; + height: 30px; .issue_assignee { @extend .span6; - float:left; + float: left; } .issue_milestone { @extend .span4; - float:left; + float: left; } } .issue_description { @@ -28,31 +28,31 @@ .issues_table { .issue { - padding:7px 10px; + padding: 7px 10px; .issue_check { - float:left; + float: left; padding: 8px 0; padding-right: 8px; min-width: 15px; } p { - padding-top:0; - padding-bottom:2px; + padding-top: 0; + padding-bottom: 2px; } img.avatar { - width:32px; - margin-top:1px; + width: 32px; + margin-top: 1px; } } } input.check_all_issues { - float:left; + float: left; padding: 0; - margin:0; + margin: 0; margin-right: 10px; position: relative; top: 8px; @@ -82,16 +82,16 @@ input.check_all_issues { } } -@media (min-width: 800px) { .issues_filters select { width:160px; } } -@media (min-width: 1000px) { .issues_filters select { width:200px; } } -@media (min-width: 1200px) { .issues_filters select { width:220px; } } +@media (min-width: 800px) { .issues_filters select { width: 160px; } } +@media (min-width: 1000px) { .issues_filters select { width: 200px; } } +@media (min-width: 1200px) { .issues_filters select { width: 220px; } } #issues-table-holder { .issues_filters { form { - padding:0; - margin:0; + padding: 0; + margin: 0; margin-top:7px } } @@ -99,48 +99,25 @@ input.check_all_issues { .issues_bulk_update { margin: 0; form { - padding:0; - margin:0; + padding: 0; + margin: 0; margin-top:7px } .update_selected_issues { - position:relative; + position: relative; top:-2px; - margin-left:4px; - float:left; + margin-left: 4px; + float: left; } .update_issues_text { - padding:3px; + padding: 3px; line-height: 18px; - float:left; + float: left; } } } #update_status { - width:100px; -} - - -/** - * Milestones list - * - */ -.milestone { - @extend .wll; -} - -/** - * Fix milestone calendar - */ - -.ui-datepicker { - border:none; - box-shadow:none; - .ui-datepicker-header { - @include solid_shade; - margin-bottom:10px; - border:1px solid #bbb; - } + width: 100px; } diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 5b8763cfec08ab7775d1b02cca78a8feb63cf6cb..8c21de70013beb369005468d4622d3bd2e5198cb 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -1,13 +1,13 @@ /* Login Page */ body.login-page{ padding-top: 10%; - background:#f1f1f1; + background: #f1f1f1; } .login-box{ width: 304px; position: relative; - border-radius: 5px; + @include border-radius(5px); margin: auto; padding: 20px; background: white; @@ -18,25 +18,15 @@ body.login-page{ display: block; } -.login-box input.text{background-color: #f1f1f1; font-size: 16px; border-radius: 0; padding: 14px 10px; width: 280px} +.login-box input.text{background-color: #f1f1f1; font-size: 16px; @include border-radius(0); padding: 14px 10px; width: 280px} .login-box input.text.top{ - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - margin-bottom:0px; + @include border-radius(5px 5px 0 0); + margin-bottom: 0px; } .login-box input.text.bottom{ - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; + @include border-radius(0 0 5px 5px); border-top: 0; margin-bottom: 20px; } diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 9087e7c2f59d9bf5b4e53ae5cacf378bdd71873a..4808117d02ad9f55f80711b6d7679b347b710502 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -5,10 +5,10 @@ .mr_branch_box { @extend .ui-box; - margin-bottom:20px; + margin-bottom: 20px; .body { - background:#f1f1f1; + background: #f1f1f1; } } @@ -23,31 +23,30 @@ } form { - margin-bottom:0; + margin-bottom: 0; .clearfix { - margin-bottom:0; + margin-bottom: 0; } } .accept_group { - float:left; + float: left; border: 1px solid #ADA; padding: 2px; @include border-radius(5px); - border-radius: 5px; background: #CEB; .accept_merge_request { - font-size:13px; - float:left; + font-size: 13px; + float: left; } .remove_branch_holder { - margin-left:20px; - margin-right:10px; - float:left; + margin-left: 20px; + margin-right: 10px; + float: left; } label { - color:#444; + color: #444; } } @@ -60,15 +59,15 @@ .mr_nav_tabs { li { a { - font-weight:bold; - padding:8px 20px; - text-align:center; + font-weight: bold; + padding: 8px 20px; + text-align: center; } } } li.merge_request { - padding:7px 10px; + padding: 7px 10px; img.avatar { width: 32px; margin-top: 1px; @@ -85,35 +84,35 @@ li.merge_request { } .label_branch { - @include round-borders-all(4px); - padding:2px 4px; - border:none; - font-size:14px; + @include border-radius(4px); + padding: 2px 4px; + border: none; + font-size: 14px; background: #474D57; - color:#fff; + color: #fff; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } .mr_source_commit, .mr_target_commit { .commit { - margin:0; - padding:0; + margin: 0; + padding: 0; padding: 5px; margin-bottom: 5px; .avatar { position:relative } .row_title { - color:#444; + color: #444; } .commit-author-name, .dash, .committed_ago, .browse_code_link_holder { - display:none; + display: none; } - list-style:none; + list-style: none; &:hover { - background:none; + background: none; } } } @@ -126,20 +125,14 @@ li.merge_request { @extend .main_box; .merge_requests_middle_box { @extend .middle_box_content; - height:30px; + height: 30px; .merge_requests_assignee { @extend .span6; - float:left; + float: left; } .merge_requests_milestone { @extend .span4; - float:left; + float: left; } } } - -.status-badge { - height: 32px; - width: 100%; - @include border-radius(5px); -} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 5707216922c34c8bd559b01a88858ccc8ccf2e6c..bc19bc75a6797d0a95a99e01696913ba4560f222 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -3,64 +3,40 @@ * */ ul.main_menu { - border-radius: 4px; margin: auto; - margin:30px 0; - border:1px solid #BBB; - height:37px; - @include bg-gray-gradient; - position:relative; - overflow:hidden; - @include shade; + margin: 30px 0; + margin-top: 10px; + border-bottom: 1px solid #DDD; + height: 37px; + position: relative; + 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; - background: #f2f2f2; - border-top: 1px solid #CCC; - border-radius: 8px; - -moz-border-radius: 8px; + 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; + background: $hover; + text-shadow: none; + color: $style_color; } li { list-style-type: none; margin: 0; display: table-cell; width: 1%; - border-right: 1px solid #DDD; - border-left: 1px solid #EEE; - border-bottom:2px solid #CFCFCF; - - &:first-child{ - -webkit-border-top-left-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -moz-border-radius-topleft: 4px; - -moz-border-radius-bottomleft: 4px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - border-left: 0; - } - &.active { - background-color:#D5D5D5; - border-right: 1px solid #BBB; - border-left: 1px solid #BBB; - border-radius: 0 0 1px 1px; - &:first-child{ - border-bottom:none; - border-left:none; + border-bottom: 2px solid #474D57; + a { + color: $style_color; } } @@ -68,10 +44,10 @@ ul.main_menu { a { background: url(home_icon.PNG) no-repeat center center; text-indent:-9999px; - min-width:20px; + min-width: 20px; img { - position:relative; - top:4px; + position: relative; + top: 4px; } } } @@ -79,12 +55,12 @@ ul.main_menu { a { display: block; text-align: center; - font-weight:bold; - height:35px; - line-height:36px; - color: $style_color; - text-shadow:0 1px 1px white; - padding:0 10px; + font-weight: normal; + height: 35px; + line-height: 36px; + color: #777; + text-shadow: 0 1px 1px white; + padding: 0 10px; } } /* diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index d24d070df1ea8aab9a6be668fdf190c9f24b4c03..0c2a56d62f5e63e9d52366c65b3f623253da35d9 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -4,30 +4,30 @@ */ #notes-list, #new-notes-list { - display:block; - list-style:none; - margin:0px; - padding:0px; + display: block; + list-style: none; + margin: 0px; + padding: 0px; } .issue_notes, .wiki_notes { .note_content { - float:left; - width:400px; + float: left; + width: 400px; } } /* Note textare */ #note_note { - height:80px; - width:99%; - font-size:14px; + height: 80px; + width: 99%; + font-size: 14px; } #new_note { .attach_holder { - display:none; + display: none; } } @@ -36,34 +36,34 @@ border: 1px solid #ddd; padding: 10px; min-height: 60px; - background:#f5f5f5; + background: #f5f5f5; } .note { padding: 8px 0; overflow: hidden; display: block; - position:relative; + position: relative; img {float: left; margin-right: 10px;} - img.emoji {float:none;margin:0;} + img.emoji {float: none;margin: 0;} .note-author cite{font-style: italic;} - p { color:$style_color; } + p { color: $style_color; } .note-author { color: $style_color;} - .note-title { margin-left:45px; padding-top: 5px;} + .note-title { margin-left: 45px; padding-top: 5px;} .avatar { - margin-top:3px; + margin-top: 3px; } .delete-note { - display:none; - position:absolute; - right:0; - top:0; + display: none; + position: absolute; + right: 0; + top: 0; } &:hover { - .delete-note { display:block; } + .delete-note { display: block; } } } #notes-list:not(.reversed) .note, @@ -94,30 +94,31 @@ p.notify_controls span{ } tr.line_notes_row { - border-bottom:1px solid #DDD; + border-bottom: 1px solid #DDD; border-left: 7px solid #2A79A3; &.reply { - background:#eee; + background: #eee; border-left: 7px solid #2A79A3; - border-top:1px solid #ddd; + border-top: 1px solid #ddd; td { - padding:7px 10px; + padding: 7px 10px; } a.line_note_reply_link { - @include round-borders-all(4px); + border: 1px solid #eaeaea; + @include border-radius(4px); padding: 3px 10px; - margin-left:5px; + margin-left: 5px; color: white; background: #2A79A3; border-color: #2A79A3; } } ul { - margin:0; + margin: 0; li { - padding:0; - border:none; + padding: 0; + border: none; } } } @@ -125,28 +126,28 @@ tr.line_notes_row { .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .per_line_form { - background:#f5f5f5; - border-top:1px solid #eee; + background: #f5f5f5; + border-top: 1px solid #eee; form { margin: 0; } td { - border-bottom:1px solid #ddd; + border-bottom: 1px solid #ddd; } .note_actions { - margin:0; + margin: 0; padding-top: 10px; .buttons { - float:left; - width:300px; + float: left; + width: 300px; } .options { .labels { - float:left; - padding-left:10px; + float: left; + padding-left: 10px; label { padding: 6px 0; margin: 0; - width:120px; + width: 120px; } } } @@ -154,13 +155,13 @@ tr.line_notes_row { } td .line_note_link { - position:absolute; + position: absolute; margin-left:-70px; margin-top:-10px; - z-index:10; + z-index: 10; background: url("comment_add.png") no-repeat left 0; - width:32px; - height:32px; + width: 32px; + height: 32px; opacity: 0.0; filter: alpha(opacity=0); @@ -180,13 +181,13 @@ td .line_note_link { .new_note { .input-file { font: 500px monospace; - opacity:0; + opacity: 0; filter: alpha(opacity=0); position: absolute; z-index: 1; - top:0; - right:0; - padding:0; + top: 0; + right: 0; + padding: 0; margin: 0; } @@ -198,24 +199,24 @@ td .line_note_link { } .attachments { - position:relative; + position: relative; width: 350px; height: 50px; - overflow:hidden; + overflow: hidden; margin:0 0 5px !important; .input_file { .file_upload { position: absolute; - right:14px; - top:7px; + right: 14px; + top: 7px; } .file_name { - line-height:30px; - width:240px; - height:28px; - overflow:hidden; + line-height: 30px; + width: 240px; + height: 28px; + overflow: hidden; } .input-file { width: 260px; @@ -228,5 +229,5 @@ td .line_note_link { .note-text { border: 1px solid #aaa; - box-shadow:none; + box-shadow: none; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index e945ca009183f9c409d0a06a4cfa4bee488a6abc..607daf7a97eed003278917c380d97d9d46fd59f1 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -1,21 +1,21 @@ .profile_history { .event_feed { - min-height:20px; + min-height: 20px; .avatar { - width:20px; + width: 20px; } } } .profile_avatar_holder { - float:left; - width:60px; - height:60px; - margin-right:20px; + float: left; + width: 60px; + height: 60px; + margin-right: 20px; img { - width:60px; - height:60px; - background:#fff; + width: 60px; + height: 60px; + background: #fff; padding: 1px; border: 1px solid #ddd; } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index c9d386ab5bb71caae96d813c0738bbbfe4bc9d31..717f85024ccc3e1b49ceddba7767bab15fdcf94f 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -4,50 +4,34 @@ } .side { - @extend .span4; @extend .right; .groups_box, .projects_box { - h5 { - color:$style_color; - font-size:16px; + > h5 { + color: $style_color; + font-size: 16px; text-shadow: 0 1px 1px #fff; padding: 2px 10px; - line-height:32px; - font-size:14px; + line-height: 32px; + font-size: 14px; } - ul { - li { - padding:0; - a { - display:block; - .group_name { - font-size:14px; - line-height:18px; - } - .project_name { - color:#4fa2bd; - font-size:14px; - line-height:18px; - } - .arrow { - float:right; - padding:10px; - margin:0; - } - .last_activity { - padding-top:5px; - display:block; - span, strong { - font-size:12px; - color:#666; - } - } + .nav-projects-tabs li { padding: 0; } + .well-list { + .arrow { + float: right; + padding: 10px; + margin: 0; + } + .last_activity { + padding-top: 5px; + display: block; + span, strong { + font-size: 12px; + color: #666; } } } - @extend .leftbar; @extend .ui-box; } } @@ -58,21 +42,27 @@ .project_name_holder { input, label { - font-size:16px; - line-height:20px; - padding:8px; + font-size: 16px; + line-height: 20px; + padding: 8px; } label { - color:#888; + color: #888; } .btn { - padding:6px 10px; - margin-left:10px; - margin-bottom:8px; + padding: 6px 10px; + margin-left: 10px; + margin-bottom: 8px; } } .adv_settings { - h6 { margin-left:40px; } + h6 { margin-left: 40px; } + } + + fieldset.features { + .control-label { + font-weight: bold; + } } } @@ -81,19 +71,20 @@ @include bg-gray-gradient; padding: 4px 7px; border: 1px solid #CCC; - margin-bottom:20px; + margin-bottom: 20px; } .project_clone_holder { input[type="text"], .btn { - font-size:12px; + font-size: 12px; line-height: 18px; margin: 0; padding: 3px 10px; } input[type="text"] { + @extend .monospace; border: 1px solid #BBB; box-shadow: none; margin-left: -1px; @@ -102,11 +93,33 @@ .save-project-loader { img { - margin-top:50px; - margin-bottom:50px; + margin-top: 50px; + margin-bottom: 50px; } h3 { @extend .page_title; } } + +ul.nav.nav-projects-tabs { + @extend .nav-tabs; + + padding-left: 8px; + + li { + a { + padding: 4px 20px; + margin-top: 2px; + border-color: #DDD; + background-color: #EEE; + text-shadow: 0 1px 1px white; + color: #555; + } + &.active { + a { + font-weight: bold; + } + } + } +} diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/sections/snippets.scss new file mode 100644 index 0000000000000000000000000000000000000000..3944814fc3e5bf5d5ec1dcbe86ad2dfd8c3ac63e --- /dev/null +++ b/app/assets/stylesheets/sections/snippets.scss @@ -0,0 +1,9 @@ +.snippet.file_holder { + .file_title { + .snippet-file-name { + position: relative; + top: -4px; + left: -4px; + } + } +} diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/sections/themes.scss index 2d121519b02d29fe5b4ef8bfda192ff92e1008a6..4e5eaf575ae89579cc8600e5afd669a09e533825 100644 --- a/app/assets/stylesheets/sections/themes.scss +++ b/app/assets/stylesheets/sections/themes.scss @@ -6,17 +6,17 @@ } .themes_opts { - padding-left:20px; + padding-left: 20px; label { - width:175px; - margin-right:40px; + width: 175px; + margin-right: 40px; .prev { @extend .thumbnail; - height:30px; - width:175px; - margin-bottom:10px; + height: 30px; + width: 175px; + margin-bottom: 10px; &.classic { background: #31363e; @@ -42,17 +42,17 @@ } .code_highlight_opts { - padding-left:20px; + padding-left: 20px; label { - width:220px; - margin-right:40px; + width: 220px; + margin-right: 40px; .prev { @extend .thumbnail; - height:151px; - width:220px; - margin-bottom:10px; + height: 151px; + width: 220px; + margin-bottom: 10px; } } } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index f6bdb0f3dba774ef13602f5951be537057c5c6bb..b0d795f4d5aa9308c5c56eb33d0b132e7414aa73 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -1,14 +1,14 @@ .tree-holder { .tree-content-holder { - float:left; - width:100%; + float: left; + width: 100%; } .tree_progress { - display:none; - margin:20px; + display: none; + margin: 20px; &.loading { - display:block; + display: block; } } @@ -18,20 +18,20 @@ &:hover { td { background: $hover; - border-top:1px solid #ADF; - border-bottom:1px solid #ADF; + border-top: 1px solid #ADF; + border-bottom: 1px solid #ADF; } - cursor:pointer; + cursor: pointer; } } } .tree-item { .tree-item-file-name { - vertical-align:middle; + vertical-align: middle; a { &:hover { - color:$blue_link; + color: $primary_color; } } @@ -48,8 +48,8 @@ padding: 2px 10px; } td { - line-height:20px; - background:#fafafa; + line-height: 20px; + background: #fafafa; } } @@ -86,7 +86,7 @@ .tree-btn-group { .btn { margin-right:-3px; - padding:2px 10px; + padding: 2px 10px; } } diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss new file mode 100644 index 0000000000000000000000000000000000000000..4686f5422dc54e21b5fafca45d347bf007cb1d8b --- /dev/null +++ b/app/assets/stylesheets/sections/votes.scss @@ -0,0 +1,43 @@ +.votes { + font-size: 13px; + line-height: 15px; + .progress { + height: 4px; + margin: 0; + .bar { + float: left; + height: 100%; + } + .bar-success { + @include linear-gradient(#62C462, #51A351); + background-color: #468847; + } + .bar-danger { + @include linear-gradient(#EE5F5B, #BD362F); + background-color: #B94A48; + } + } + .upvotes { + display: inline-block; + color: #468847; + } + .downvotes { + display: inline-block; + color: #B94A48; + } +} +.votes-block { + margin: 14px 6px 6px 0; + .downvotes { + float: right; + } +} +.votes-inline { + display: inline-block; + margin: 0 8px; + .progress { + display: inline-block; + padding: 0 0 2px; + width: 45px; + } +} diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 1f3d3d3d3895d2056403488ce9455b5b137b5a95..b377727779aa38fa5fbb6590235cfde19c29d17c 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -4,18 +4,6 @@ * */ .ui_basic { - /* - * Common styles - * - */ - a { - color: $link_color; - &:hover { - text-decoration:none; - color: $blue_link; - } - } - .app_logo { .separator { margin-left: 0; diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index a9d2124130d364f6c3186457b4f6305a73afa49b..9e6433c5f9e18ea4ad0003323cad55da4273e06a 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -47,17 +47,17 @@ a { h1 { background: url('logo_white.png') no-repeat 0px 2px; - color:#eee; + color: #eee; text-shadow: 0 1px 1px #111; } } .separator { - display:none; + display: none; } } .project_name { - color:#eee; + color: #eee; text-shadow: 0 1px 1px #111; } } diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb index f3949149a06cc5317a062e5c3c0cfee947ae095f..9f8299f52f7b900f302eae9540631468e72bf5a1 100644 --- a/app/contexts/notes/load_context.rb +++ b/app/contexts/notes/load_context.rb @@ -19,8 +19,6 @@ module Notes when "wall" # this is the only case, where the order is DESC project.common_notes.order("created_at DESC, id DESC").limit(50) - when "wiki" - project.wiki_notes.limit(20) end @notes = if after_id diff --git a/app/contexts/project_update_context.rb b/app/contexts/project_update_context.rb new file mode 100644 index 0000000000000000000000000000000000000000..5b77d0a79648f7bed60cb06507bee51935af797e --- /dev/null +++ b/app/contexts/project_update_context.rb @@ -0,0 +1,23 @@ +class ProjectUpdateContext < 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 + + project.update_attributes(params[:project], as: role) + end +end + diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 0bba019918f69730f8ea37dc5797493281f9c6e8..a492e66611f0f4724d11f5d4b17317565bf707e2 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -2,7 +2,7 @@ class Admin::GroupsController < AdminController before_filter :group, only: [:edit, :show, :update, :destroy, :project_update] def index - @groups = Group.scoped + @groups = Group.order('name ASC') @groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.page(params[:page]).per(20) end @@ -11,6 +11,7 @@ class Admin::GroupsController < AdminController @projects = Project.scoped @projects = @projects.not_in_group(@group) if @group.projects.present? @projects = @projects.all + @projects.reject!(&:empty_repo?) end def new @@ -22,6 +23,7 @@ class Admin::GroupsController < AdminController def create @group = Group.new(params[:group]) + @group.path = @group.name.dup.parameterize if @group.name @group.owner = current_user if @group.save @@ -48,15 +50,17 @@ class Admin::GroupsController < AdminController def project_update project_ids = params[:project_ids] - Project.where(id: project_ids).update_all(group_id: @group.id) + + Project.where(id: project_ids).each do |project| + project.transfer(@group) + end redirect_to :back, notice: 'Group was successfully updated.' end def remove_project @project = Project.find(params[:project_id]) - @project.group_id = nil - @project.save + @project.transfer(nil) redirect_to :back, notice: 'Group was successfully updated.' end @@ -70,6 +74,6 @@ class Admin::GroupsController < AdminController private def group - @group = Group.find_by_code(params[:id]) + @group = Group.find_by_path(params[:id]) end end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index d27b657de3a2acd8c7840f1b362eeeeff231854a..4fea8709b703b96ec6c0a06b078002c60e7a7614 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,65 +1,51 @@ class Admin::ProjectsController < AdminController - before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update] + before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] def index - @admin_projects = Project.scoped - @admin_projects = @admin_projects.search(params[:name]) if params[:name].present? - @admin_projects = @admin_projects.order("name ASC").page(params[:page]).per(20) + @projects = Project.scoped + @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present? + @projects = @projects.where(namespace_id: nil) if params[:namespace_id] == Namespace.global_id + @projects = @projects.search(params[:name]) if params[:name].present? + @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) end def show - @users = User.scoped - @users = @users.not_in_project(@admin_project) if @admin_project.users.present? + @users = User.active + @users = @users.not_in_project(@project) if @project.users.present? @users = @users.all end - def new - @admin_project = Project.new - end - def edit end def team_update - @admin_project.add_users_ids_to_team(params[:user_ids], params[:project_access]) - - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.' - end - - def create - @admin_project = Project.new(params[:project]) - @admin_project.owner = current_user + @project.add_users_ids_to_team(params[:user_ids], params[:project_access]) - if @admin_project.save - redirect_to [:admin, @admin_project], notice: 'Project was successfully created.' - else - render action: "new" - end + redirect_to [:admin, @project], notice: 'Project was successfully updated.' end def update - owner_id = params[:project].delete(:owner_id) + status = ProjectUpdateContext.new(project, current_user, params).execute(:admin) - if owner_id - @admin_project.owner = User.find(owner_id) - end - - if @admin_project.update_attributes(params[:project]) - redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.' + if status + redirect_to [:admin, @project], notice: 'Project was successfully updated.' else render action: "edit" end end def destroy - @admin_project.destroy + @project.destroy - redirect_to admin_projects_url, notice: 'Project was successfully deleted.' + redirect_to admin_projects_path, notice: 'Project was successfully deleted.' end - private + protected + + def project + id = params[:project_id] || params[:id] - def admin_project - @admin_project = Project.find_by_code(params[:id]) + @project = Project.find_with_namespace(id) + @project || render_404 end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 744b1912a6ca8a3ce573acf316a0bca3fda8a8c7..5f259bd7e27b69e8488f5fd5637bfb72b8b86124 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -3,7 +3,7 @@ class Admin::UsersController < AdminController @admin_users = User.scoped @admin_users = @admin_users.filter(params[:filter]) @admin_users = @admin_users.search(params[:name]) if params[:name].present? - @admin_users = @admin_users.order("updated_at DESC").page(params[:page]) + @admin_users = @admin_users.order("name ASC").page(params[:page]) end def show @@ -30,7 +30,7 @@ class Admin::UsersController < AdminController def new - @admin_user = User.new({ projects_limit: Gitlab.config.default_projects_limit }, as: :admin) + @admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin) end def edit diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ef6fc81a5d5e02aff566ae928252d983a215c4c0..75cd8f156052a64363f9cd994fa8edd6bf2d8e71 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :set_current_user_for_observers + before_filter :add_abilities before_filter :dev_tools if Rails.env == 'development' protect_from_forgery @@ -34,7 +35,7 @@ class ApplicationController < ActionController::Base def reject_blocked! if current_user && current_user.blocked sign_out current_user - flash[:alert] = "Your account was blocked" + flash[:alert] = "Your account is blocked. Retry when an admin unblock it." redirect_to new_user_session_path end end @@ -42,7 +43,7 @@ class ApplicationController < ActionController::Base def after_sign_in_path_for resource if resource.is_a?(User) && resource.respond_to?(:blocked) && resource.blocked sign_out resource - flash[:alert] = "Your account was blocked" + flash[:alert] = "Your account is blocked. Retry when an admin unblock it." new_user_session_path else super @@ -63,11 +64,19 @@ class ApplicationController < ActionController::Base end def project - @project ||= current_user.projects.find_by_code(params[:project_id] || params[:id]) - @project || render_404 + id = params[:project_id] || params[:id] + + @project = Project.find_with_namespace(id) + + if @project and can?(current_user, :read_project, @project) + @project + else + @project = nil + render_404 + end end - def add_project_abilities + def add_abilities abilities << Ability end @@ -103,6 +112,10 @@ class ApplicationController < ActionController::Base render file: Rails.root.join("public", "404"), layout: false, status: "404" end + def render_403 + render file: Rails.root.join("public", "403"), layout: false, status: "403" + end + def require_non_empty_project redirect_to @project if @project.empty_repo? end diff --git a/app/controllers/commit_controller.rb b/app/controllers/commit_controller.rb index 97998352255ff6e1aa47b64cd2e1031bcf9753e1..d671e9f9e9e1721fdf59ee1631c814f7fb92d626 100644 --- a/app/controllers/commit_controller.rb +++ b/app/controllers/commit_controller.rb @@ -26,7 +26,8 @@ class CommitController < ProjectResourceController end end - format.patch + format.diff { render text: @commit.to_diff } + format.patch { render text: @commit.to_patch } end end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 8d9329f2b32e0f5d03c31fbe59da544c69d6e105..1fcadbfefbaf4d015d68ab7bc349f67becb717df 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,11 +1,23 @@ class DashboardController < ApplicationController respond_to :html + before_filter :projects before_filter :event_filter, only: :index def index - @groups = Group.where(id: current_user.projects.pluck(:group_id)) - @projects = current_user.projects_sorted_by_activity + @groups = current_user.authorized_groups + + @has_authorized_projects = @projects.count > 0 + + @projects = case params[:scope] + when 'personal' then + @projects.personal(current_user) + when 'joined' then + @projects.joined(current_user) + else + @projects + end + @projects = @projects.page(params[:page]).per(30) @events = Event.in_projects(current_user.project_ids) @@ -23,15 +35,16 @@ class DashboardController < ApplicationController # Get authored or assigned open merge requests def merge_requests - @projects = current_user.projects.all - @merge_requests = current_user.cared_merge_requests.recent.page(params[:page]).per(20) + @merge_requests = current_user.cared_merge_requests + @merge_requests = dashboard_filter(@merge_requests) + @merge_requests = @merge_requests.recent.page(params[:page]).per(20) end # Get only assigned issues def issues - @projects = current_user.projects.all - @user = current_user - @issues = current_user.assigned_issues.opened.recent.page(params[:page]).per(20) + @issues = current_user.assigned_issues + @issues = dashboard_filter(@issues) + @issues = @issues.recent.page(params[:page]).per(20) @issues = @issues.includes(:author, :project) respond_to do |format| @@ -40,7 +53,32 @@ class DashboardController < ApplicationController end end + protected + + def projects + @projects = current_user.authorized_projects.sorted_by_activity + end + def event_filter @event_filter ||= EventFilter.new(params[:event_filter]) end + + def dashboard_filter items + if params[:project_id] + items = items.where(project_id: params[:project_id]) + end + + if params[:search].present? + items = items.search(params[:search]) + end + + case params[:status] + when 'closed' + items.closed + when 'all' + items + else + items.opened + end + end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 63f70cd00275dc9b57f072ade08b2055378cc545..c82edb4c168bc09476e0decc879ad95423bcff05 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -5,6 +5,9 @@ class GroupsController < ApplicationController before_filter :group before_filter :projects + # Authorize + before_filter :authorize_read_group! + def show @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) @last_push = current_user.recent_push @@ -18,7 +21,7 @@ class GroupsController < ApplicationController # Get authored or assigned open merge requests def merge_requests - @merge_requests = current_user.cared_merge_requests + @merge_requests = current_user.cared_merge_requests.opened @merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20) end @@ -44,20 +47,33 @@ class GroupsController < ApplicationController end def people - @users = group.users.all + @project = group.projects.find(params[:project_id]) if params[:project_id] + @users = @project ? @project.users : group.users + @users.sort_by!(&:name) + + if @project + @team_member = @project.users_projects.new + end end protected def group - @group ||= Group.find_by_code(params[:id]) + @group ||= Group.find_by_path(params[:id]) end def projects - @projects ||= current_user.projects_sorted_by_activity.where(group_id: @group.id) + @projects ||= group.projects.authorized_for(current_user).sorted_by_activity end def project_ids projects.map(&:id) end + + # Dont allow unauthorized access to group + def authorize_read_group! + unless projects.present? or can?(current_user, :manage_group, @group) + return render_404 + end + end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 0f28fc3a111e9c09d7b08ac3806de9a68fc776be..5a1ce2cfcc40ffeeeeba67950c2a954d1a3c54ba 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -1,6 +1,6 @@ class IssuesController < ProjectResourceController before_filter :module_enabled - before_filter :issue, only: [:edit, :update, :destroy, :show] + before_filter :issue, only: [:edit, :update, :show] # Allow read any issue before_filter :authorize_read_issue! @@ -11,9 +11,6 @@ class IssuesController < ProjectResourceController # Allow modify issue before_filter :authorize_modify_issue!, only: [:edit, :update] - # Allow destroy issue - before_filter :authorize_admin_issue!, only: [:destroy] - respond_to :js, :html def index @@ -77,15 +74,6 @@ class IssuesController < ProjectResourceController end end - def destroy - @issue.destroy - - respond_to do |format| - format.html { redirect_to project_issues_path } - format.js { render nothing: true } - end - end - def sort return render_404 unless can?(current_user, :admin_issue, @project) diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 8e180c9baae7cd78129755f84ceced37d4bc54cc..fa4eaff84698806faf5d99782984c1b26c72822e 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -1,7 +1,7 @@ class MergeRequestsController < ProjectResourceController before_filter :module_enabled - before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check, :raw] - before_filter :validates_merge_request, only: [:show, :diffs, :raw] + before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status] + before_filter :validates_merge_request, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs] # Allow read any merge_request @@ -13,10 +13,6 @@ class MergeRequestsController < ProjectResourceController # Allow modify merge_request before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] - # Allow destroy merge_request - before_filter :authorize_admin_merge_request!, only: [:destroy] - - def index @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute end @@ -25,11 +21,10 @@ class MergeRequestsController < ProjectResourceController respond_to do |format| format.html format.js - end - end - def raw - send_file @merge_request.to_raw + format.diff { render text: @merge_request.to_diff } + format.patch { render text: @merge_request.to_patch } + end end def diffs @@ -87,14 +82,6 @@ class MergeRequestsController < ProjectResourceController end end - def destroy - @merge_request.destroy - - respond_to do |format| - format.html { redirect_to project_merge_requests_url(@project) } - end - end - def branch_from @commit = project.commit(params[:ref]) @commit = CommitDecorator.decorate(@commit) @@ -105,6 +92,13 @@ class MergeRequestsController < ProjectResourceController @commit = CommitDecorator.decorate(@commit) end + def ci_status + status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) + response = { status: status } + + render json: response + end + protected def merge_request diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb index fadfee2dc0625eff7788d8bd385f002fa7f3263b..a0c824e8abb0919006ab1c151a13b53ad25ac398 100644 --- a/app/controllers/milestones_controller.rb +++ b/app/controllers/milestones_controller.rb @@ -12,11 +12,12 @@ class MilestonesController < ProjectResourceController def index @milestones = case params[:f] - when 'all'; @project.milestones - else @project.milestones.active + when 'all'; @project.milestones.order("closed, due_date DESC") + when 'closed'; @project.milestones.closed.order("due_date DESC") + else @project.milestones.active.order("due_date ASC") end - @milestones = @milestones.includes(:project).order("due_date") + @milestones = @milestones.includes(:project) @milestones = @milestones.page(params[:page]).per(20) end @@ -42,6 +43,7 @@ class MilestonesController < ProjectResourceController def create @milestone = @project.milestones.new(params[:milestone]) + @milestone.author_id_of_changes = current_user.id if @milestone.save redirect_to project_milestone_path(@project, @milestone) @@ -51,7 +53,7 @@ class MilestonesController < ProjectResourceController end def update - @milestone.update_attributes(params[:milestone]) + @milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id)) respond_to do |format| format.js diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 2fb783b289b00e2b6a68a9424c18596806e32343..c4ebf0e48896b511b7c75cec48624a90edefa685 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,5 +1,5 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController - Gitlab.config.omniauth_providers.each do |provider| + Gitlab.config.omniauth.providers.each do |provider| define_method provider['name'] do handle_omniauth end diff --git a/app/controllers/profile_controller.rb b/app/controllers/profiles_controller.rb similarity index 53% rename from app/controllers/profile_controller.rb rename to app/controllers/profiles_controller.rb index 5f8b11fdded2e98cd495a1d7f59f8fff2e0291ad..1d1efb16f042c0cef0c3200756b660715553b4bf 100644 --- a/app/controllers/profile_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -1,5 +1,6 @@ -class ProfileController < ApplicationController +class ProfilesController < ApplicationController before_filter :user + layout 'profile' def show end @@ -7,8 +8,15 @@ class ProfileController < ApplicationController def design end + def account + end + def update - @user.update_attributes(params[:user]) + if @user.update_attributes(params[:user]) + flash[:notice] = "Profile was successfully updated" + else + flash[:alert] = "Failed to update profile" + end respond_to do |format| format.html { redirect_to :back } @@ -19,7 +27,7 @@ class ProfileController < ApplicationController def token end - def password_update + def update_password params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"} if @user.update_attributes(params[:user]) @@ -31,14 +39,25 @@ class ProfileController < ApplicationController end def reset_private_token - current_user.reset_authentication_token! - redirect_to profile_account_path + if current_user.reset_authentication_token! + flash[:notice] = "Token was successfully updated" + end + + redirect_to account_profile_path end def history @events = current_user.recent_events.page(params[:page]).per(20) end + def update_username + @user.update_attributes(username: params[:user][:username]) + + respond_to do |format| + format.js + end + end + private def user diff --git a/app/controllers/project_resource_controller.rb b/app/controllers/project_resource_controller.rb index d297bba635fa4e3031a8dc84582c4e8368953d9b..81bc3a91bd1ab07d1ed2c4752208745b8d83b64f 100644 --- a/app/controllers/project_resource_controller.rb +++ b/app/controllers/project_resource_controller.rb @@ -1,5 +1,3 @@ class ProjectResourceController < ApplicationController before_filter :project - # Authorize - before_filter :add_project_abilities end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 72080070bed7a1e948aa4d9d6944dcf0857eae8b..271647c783c0d896e190f59804a0d028dbbbfddf 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -34,8 +34,11 @@ class ProjectsController < ProjectResourceController end def update + status = ProjectUpdateContext.new(project, current_user, params).execute + respond_to do |format| - if project.update_attributes(params[:project]) + if status + flash[:notice] = 'Project was successfully updated.' format.html { redirect_to edit_project_path(project), notice: 'Project was successfully updated.' } format.js else @@ -43,6 +46,10 @@ class ProjectsController < ProjectResourceController format.js end end + + rescue Project::TransferError => ex + @error = ex + render :update_failed end def show @@ -51,12 +58,12 @@ class ProjectsController < ProjectResourceController respond_to do |format| format.html do - unless @project.empty_repo? - @last_push = current_user.recent_push(@project.id) - render :show - else - render "projects/empty" - end + unless @project.empty_repo? + @last_push = current_user.recent_push(@project.id) + render :show + else + render "projects/empty" + end end format.js end @@ -80,12 +87,18 @@ class ProjectsController < ProjectResourceController end def graph - graph = Gitlab::Graph::JsonBuilder.new(project) - - @days_json, @commits_json = graph.days_json, graph.commits_json + respond_to do |format| + format.html + format.json do + graph = Gitlab::Graph::JsonBuilder.new(project) + render :json => graph.to_json + end + end end def destroy + return access_denied! unless can?(current_user, :remove_project, project) + # Disable the UsersProject update_repository call, otherwise it will be # called once for every person removed from the project UsersProject.skip_callback(:destroy, :after, :update_repository) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 7324a4594ebb5f9e84bbf6b543aefb4af2384d2e..d037ad11ef1cf8e10751d857cbe397a199619473 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController respond_to :html def index - @snippets = @project.snippets + @snippets = @project.snippets.fresh end def new @@ -60,7 +60,7 @@ class SnippetsController < ProjectResourceController redirect_to project_snippets_path(@project) end - def raw + def raw send_data( @snippet.content, type: "text/plain", diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 37ed74b231f0081cb52e332a368b537604739f87..311af62b8db87d22d74707bb1bbc8008243bbdd7 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -21,7 +21,11 @@ class TeamMembersController < ProjectResourceController params[:project_access] ) - redirect_to project_team_index_path(@project) + if params[:redirect_to] + redirect_to params[:redirect_to] + else + redirect_to project_team_index_path(@project) + end end def update diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb index 69d5b178214a40382e085e1c9e8100e07dd1e159..a066b2e4a2603f2b8f26964d16a9830fbef4c8d3 100644 --- a/app/decorators/commit_decorator.rb +++ b/app/decorators/commit_decorator.rb @@ -76,7 +76,7 @@ class CommitDecorator < ApplicationDecorator 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] + 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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cba34c963a0b9db60f820f31cf0ccc7b7d0adff1..52715a265bd49e0cc54a0f524351c15a70ea3c66 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,5 @@ require 'digest/md5' +require 'uri' module ApplicationHelper @@ -30,13 +31,15 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end - def gravatar_icon(user_email = '', size = 40) - if Gitlab.config.disable_gravatar? || user_email.blank? + def gravatar_icon(user_email = '', size = nil) + size = 40 if size.nil? || size <= 0 + + if !Gitlab.config.gravatar.enabled || user_email.blank? 'no_avatar.png' else - gravatar_prefix = request.ssl? ? "https://secure" : "http://www" + gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url user_email.strip! - "#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=mm" + sprintf(gravatar_url, {:hash => Digest::MD5.hexdigest(user_email.downcase), :email => URI.escape(user_email), :size => size}) end end @@ -45,7 +48,7 @@ module ApplicationHelper end def web_app_url - "#{request_protocol}://#{Gitlab.config.web_host}/" + "#{request_protocol}://#{Gitlab.config.gitlab.host}/" end def last_commit(project) @@ -75,7 +78,7 @@ module ApplicationHelper end def search_autocomplete_source - projects = current_user.projects.map{ |p| { label: p.name, url: project_path(p) } } + projects = current_user.projects.map{ |p| { label: p.name_with_namespace, url: project_path(p) } } default_nav = [ { label: "My Profile", url: profile_path }, @@ -92,6 +95,7 @@ module ApplicationHelper { label: "API Help", url: help_api_path }, { label: "Markdown Help", url: help_markdown_path }, { label: "SSH Keys Help", url: help_ssh_path }, + { label: "Gitlab Rake Tasks Help", url: help_raketasks_path }, ] project_nav = [] @@ -126,6 +130,10 @@ module ApplicationHelper Gitlab::Theme.css_class_by_id(current_user.try(:theme_id)) end + def user_color_scheme_class + current_user.dark_scheme ? :black : :white + end + def show_last_push_widget?(event) event && event.last_push_to_non_root? && diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..0baa5b4108ee3b049c944d375ac18102252e79c9 --- /dev/null +++ b/app/helpers/dashboard_helper.rb @@ -0,0 +1,32 @@ +module DashboardHelper + def dashboard_filter_path(entity, options={}) + exist_opts = { + status: params[:status], + project_id: params[:project_id], + } + + options = exist_opts.merge(options) + + case entity + when 'issue' then + dashboard_issues_path(options) + when 'merge_request' + dashboard_merge_requests_path(options) + end + end + + def entities_per_project project, entity + items = project.items_for(entity) + + items = case params[:status] + when 'closed' + items.closed + when 'all' + items + else + items.opened + end + + items.where(assignee_id: current_user.id).count + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 99ea9ef2975dc8e5af615a0d155d104c02ede112..2825787fd2f3c6cd2031e32801d2e6e90ce9ae17 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -4,28 +4,6 @@ module IssuesHelper project_issues_path project, params end - def link_to_issue_assignee(issue) - project = issue.project - - tm = project.team_member_by_id(issue.assignee_id) - if tm - link_to issue.assignee_name, project_team_member_path(project, tm), class: "author_link" - else - issue.assignee_name - end - end - - def link_to_issue_author(issue) - project = issue.project - - tm = project.team_member_by_id(issue.author_id) - if tm - link_to issue.author_name, project_team_member_path(project, tm), class: "author_link" - else - issue.author_name - end - end - def issue_css_classes issue classes = "issue" classes << " closed" if issue.closed @@ -52,4 +30,14 @@ module IssuesHelper open: "open" } end + + def labels_autocomplete_source + labels = @project.issues_labels.order('count DESC') + labels = labels.map{ |l| { label: l.name, value: l.name } } + labels.to_json + end + + def issues_active_milestones + @project.milestones.active.order("id desc").all + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index b23c4a8f0df1bd143bd5a1bbea4a1bdc637864c6..f48425bd6de3dac6cf61c15224a51a90c6f5a021 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,26 +1,4 @@ module MergeRequestsHelper - def link_to_merge_request_assignee(merge_request) - project = merge_request.project - - tm = project.team_member_by_id(merge_request.assignee_id) - if tm - link_to merge_request.assignee_name, project_team_member_path(project, tm), class: "author_link" - else - merge_request.assignee_name - end - end - - def link_to_merge_request_author(merge_request) - project = merge_request.project - - tm = project.team_member_by_id(merge_request.author_id) - if tm - link_to merge_request.author_name, project_team_member_path(project, tm), class: "author_link" - else - merge_request.author_name - end - end - def new_mr_path_from_push_event(event) new_project_merge_request_path( event.project, @@ -39,7 +17,7 @@ module MergeRequestsHelper classes end - def ci_status_path - @project.gitlab_ci_service.commit_badge_path(@merge_request.last_commit.sha) + def ci_build_details_path merge_request + merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha) end end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..fdf6725cc13d980ba02bc3af20ee855242b1f791 --- /dev/null +++ b/app/helpers/namespaces_helper.rb @@ -0,0 +1,26 @@ +module NamespacesHelper + def namespaces_options(selected = :current_user, scope = :default) + groups = current_user.namespaces.select {|n| n.type == 'Group'} + + users = if scope == :all + Namespace.root + else + current_user.namespaces.reject {|n| n.type == 'Group'} + end + + global_opts = ["Global", [['/', Namespace.global_id]] ] + group_opts = ["Groups", groups.map {|g| [g.human_name, g.id]} ] + users_opts = [ "Users", users.map {|u| [u.human_name, u.id]} ] + + options = [] + options << global_opts if current_user.admin + options << group_opts + options << users_opts + + if selected == :current_user && current_user.namespace + selected = current_user.namespace.id + end + + grouped_options_for_select(options, selected) + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7c302ef4176a9ce6528171f2bd3af989d1550169..425dd471b364e758977e39de8dc9046da39edef0 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -8,11 +8,49 @@ module ProjectsHelper end def link_to_project project - link_to project.name, project + link_to project do + title = content_tag(:strong, project.name) + + if project.namespace + namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny') + title = namespace + title + end + + title + end + end + + def link_to_member(project, author) + return "(deleted)" unless author + + # Build avatar image tag + avatar = image_tag(gravatar_icon(author.try(:email)), width: 16, class: "lil_av") + + # Build name strong tag + name = content_tag :strong, author.name, class: 'author' + + author_html = avatar + name + + tm = project.team_member_by_id(author) + + content_tag :span, class: 'member-link' do + if tm + link_to author_html, project_team_member_path(project, tm), class: "author_link" + else + author_html + end + end end def tm_path team_member project_team_member_path(@project, team_member) end -end + def project_title project + if project.group + project.name_with_namespace + else + project.name + end + end +end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 740700c30092c94d75ceb21f453c0c1b42d00aa5..d52d8af6641962da4f3255b3c5fa3e722d8d5d26 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -72,7 +72,7 @@ module TabHelper return "active" if current_page?(controller: "projects", action: action, id: @project) end - if ['snippets', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name + if ['snippets', 'services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name "active" end end @@ -84,4 +84,17 @@ module TabHelper 'active' end end + + # Use nav_tab for save controller/action but different params + def nav_tab key, value, &block + o = {} + o[:class] = "" + o[:class] << " active" if params[key] == value + + if block_given? + content_tag(:li, capture(&block), o) + else + content_tag(:li, nil, o) + end + end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 7f3862adb1d7995b1ce0c5af715af3438a4904f9..5cd9b82900c1b10ea8348cf8c770a7c3cdcf8f32 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -3,11 +3,11 @@ class Notify < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper - default_url_options[:host] = Gitlab.config.web_host - default_url_options[:protocol] = Gitlab.config.web_protocol - default_url_options[:port] = Gitlab.config.web_port if Gitlab.config.web_custom_port? + default_url_options[:host] = Gitlab.config.gitlab.host + default_url_options[:protocol] = Gitlab.config.gitlab.protocol + default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port? - default from: Gitlab.config.email_from + default from: Gitlab.config.gitlab.email_from @@ -31,6 +31,7 @@ class Notify < ActionMailer::Base def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) @issue = Issue.find issue_id @issue_status = status + @project = @issue.project @updated_by = User.find updated_by_user_id mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) @@ -89,14 +90,6 @@ class Notify < ActionMailer::Base mail(to: recipient(recipient_id), subject: subject) end - def note_wiki_email(recipient_id, note_id) - @note = Note.find(note_id) - @wiki = @note.noteable - @project = @note.project - mail(to: recipient(recipient_id), subject: subject("note for wiki")) - end - - # # Project @@ -105,11 +98,17 @@ class Notify < ActionMailer::Base def project_access_granted_email(user_project_id) @users_project = UsersProject.find user_project_id @project = @users_project.project - mail(to: @users_project.user.email, + mail(to: @users_project.user.email, subject: subject("access to project was granted")) end + def project_was_moved_email(user_project_id) + @users_project = UsersProject.find user_project_id + @project = @users_project.project + mail(to: @users_project.user.email, + subject: subject("project was moved")) + end # # User diff --git a/app/models/ability.rb b/app/models/ability.rb index c3a212f473dad8f848334c177a442acf6134b1a3..2d80c6720b79da74ae9e4d20d816b2433f3082a8 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,6 +7,7 @@ class Ability when "Note" then note_abilities(object, subject) when "Snippet" then snippet_abilities(object, subject) when "MergeRequest" then merge_request_abilities(object, subject) + when "Group" then group_abilities(object, subject) else [] end end @@ -14,7 +15,40 @@ class Ability def project_abilities(user, project) rules = [] - rules << [ + # Rules based on role in project + if project.master_access_for?(user) + rules << project_master_rules + + elsif project.dev_access_for?(user) + rules << project_dev_rules + + elsif project.report_access_for?(user) + rules << project_report_rules + + elsif project.guest_access_for?(user) + rules << project_guest_rules + end + + if project.namespace + # If user own project namespace + # (Ex. group owner or account owner) + if project.namespace.owner == user + rules << project_admin_rules + end + else + # For compatibility with global projects + # use projects.owner_id + if project.owner == user + rules << project_admin_rules + end + end + + + rules.flatten + end + + def project_guest_rules + [ :read_project, :read_wiki, :read_issue, @@ -26,28 +60,30 @@ class Ability :write_project, :write_issue, :write_note - ] if project.guest_access_for?(user) + ] + end - rules << [ + def project_report_rules + project_guest_rules + [ :download_code, :write_merge_request, :write_snippet - ] if project.report_access_for?(user) + ] + end - rules << [ + def project_dev_rules + project_report_rules + [ :write_wiki, :push_code - ] if project.dev_access_for?(user) - - rules << [ - :push_code_to_protected_branches - ] if project.master_access_for?(user) + ] + end - rules << [ + def project_master_rules + project_dev_rules + [ + :push_code_to_protected_branches, :modify_issue, :modify_snippet, :modify_merge_request, - :admin_project, :admin_issue, :admin_milestone, :admin_snippet, @@ -55,8 +91,25 @@ class Ability :admin_merge_request, :admin_note, :accept_mr, - :admin_wiki - ] if project.master_access_for?(user) || project.owner == user + :admin_wiki, + :admin_project + ] + end + + def project_admin_rules + project_master_rules + [ + :change_namespace, + :rename_project, + :remove_project + ] + end + + def group_abilities user, group + rules = [] + + rules << [ + :manage_group + ] if group.owner == user rules.flatten end diff --git a/app/models/commit.rb b/app/models/commit.rb index 5efb20cea3b447417f3264e7253a9e178eceef75..f11b7fe0202eca8a5d2e24f69adb99208a90bdbd 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -87,14 +87,10 @@ class Commit last = project.commit(from.try(:strip)) if first && last - commits = [first, last].sort_by(&:created_at) - younger = commits.first - older = commits.last - - result[:same] = (younger.id == older.id) - result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} - result[:diffs] = project.repo.diff(younger.id, older.id) rescue [] - result[:commit] = Commit.new(older) + result[:same] = (first.id == last.id) + result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)} + result[:diffs] = project.repo.diff(last.id, first.id) rescue [] + result[:commit] = Commit.new(first) end result @@ -150,4 +146,21 @@ class Commit def parents_count parents && parents.count || 0 end + + # Shows the diff between the commit's parent and the commit. + # + # Cuts out the header and stats from #to_patch and returns only the diff. + def to_diff + # see Grit::Commit#show + patch = to_patch + + # discard lines before the diff + lines = patch.split("\n") + while !lines.first.start_with?("diff --git") do + lines.shift + end + lines.pop if lines.last =~ /^[\d.]+$/ # Git version + lines.pop if lines.last == "-- " # end of diff + lines.join("\n") + end end diff --git a/app/models/event.rb b/app/models/event.rb index 2b92783ceac6784c6a2a606910920d2ce16b210a..90376e73753045f6df7547ae285f3f641421fbd8 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -15,6 +15,7 @@ # class Event < ActiveRecord::Base + include NoteEvent include PushEvent attr_accessible :project, :action, :data, :author_id, :project_id, @@ -58,12 +59,14 @@ class Event < ActiveRecord::Base end end - # Next events currently enabled for system - # - push - # - new issue - # - merge request - def allowed? - push? || issue? || merge_request? || membership_changed? + def proper? + if push? + true + elsif membership_changed? + true + else + (issue? || merge_request? || note? || milestone?) && target + end end def project_name @@ -94,6 +97,14 @@ class Event < ActiveRecord::Base action == self.class::Reopened end + def milestone? + target_type == "Milestone" + end + + def note? + target_type == "Note" + end + def issue? target_type == "Issue" end diff --git a/app/models/gitlab_ci_service.rb b/app/models/gitlab_ci_service.rb index 24b70323098d64378135da9d8501830439f25cd3..a2f5634a86f87b6f53d8d73b3943547dc91a60cd 100644 --- a/app/models/gitlab_ci_service.rb +++ b/app/models/gitlab_ci_service.rb @@ -36,4 +36,22 @@ class GitlabCiService < Service def commit_badge_path sha project_url + "/status?sha=#{sha}" end + + def commit_status_path sha + project_url + "/builds/#{sha}/status.json?token=#{token}" + end + + def commit_status sha + response = HTTParty.get(commit_status_path(sha)) + + if response.code == 200 and response["status"] + response["status"] + else + :error + end + end + + def build_page sha + project_url + "/builds/#{sha}" + end end diff --git a/app/models/group.rb b/app/models/group.rb index 1ff6872f6874bb2737dd0a411fc318b75b148ab6..b668f5560ab1ca9a7761d7ed133b517f29a2a5aa 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,36 +1,24 @@ # == Schema Information # -# Table name: groups +# Table name: namespaces # # id :integer not null, primary key # name :string(255) not null -# code :string(255) not null +# path :string(255) not null # owner_id :integer not null # created_at :datetime not null # updated_at :datetime not null +# type :string(255) # -class Group < ActiveRecord::Base - attr_accessible :code, :name, :owner_id - - has_many :projects - belongs_to :owner, class_name: "User" - - validates :name, presence: true, uniqueness: true - validates :code, presence: true, uniqueness: true - validates :owner, presence: true - - delegate :name, to: :owner, allow_nil: true, prefix: true - - def self.search query - where("name LIKE :query OR code LIKE :query", query: "%#{query}%") - end - - def to_param - code +class Group < Namespace + def users + users = User.joins(:users_projects).where(users_projects: {project_id: project_ids}) + users = users << owner + users.uniq end - def users - User.joins(:users_projects).where(users_projects: {project_id: project_ids}).uniq + def human_name + name end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0766e5baa7275dfa177dec0388f63d45be28f0d0..052e0850d9623d63eaa12bd08cf5e965b387172a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -202,20 +202,26 @@ class MergeRequest < ActiveRecord::Base false end - def to_raw - FileUtils.mkdir_p(Rails.root.join("tmp", "patches")) - patch_path = Rails.root.join("tmp", "patches", "merge_request_#{self.id}.patch") - - from = commits.last.id - to = source_branch + def mr_and_commit_notes + commit_ids = commits.map(&:id) + Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) + end - project.repo.git.run('', "format-patch" , " > #{patch_path.to_s}", {}, ["#{from}..#{to}", "--stdout"]) + # Returns the raw diff for this merge request + # + # see "git diff" + def to_diff + project.repo.git.native(:diff, {timeout: 30, raise: true}, "#{target_branch}...#{source_branch}") + end - patch_path + # Returns the commit as a series of email patches. + # + # see "git format-patch" + def to_patch + project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}") end - def mr_and_commit_notes - commit_ids = commits.map(&:id) - Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND noteable_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) + def last_commit_short_sha + @last_commit_short_sha ||= last_commit.sha[0..10] end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index a50831a2241ce2cc102921910170175c4c2a54a6..4fac9bec2591318fca1ddc6332ea546a828f6b5c 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -13,18 +13,26 @@ # class Milestone < ActiveRecord::Base - attr_accessible :title, :description, :due_date, :closed + attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes + attr_accessor :author_id_of_changes belongs_to :project has_many :issues has_many :merge_requests + scope :active, where(closed: false) + scope :closed, where(closed: true) + validates :title, presence: true validates :project, presence: true validates :closed, inclusion: { in: [true, false] } - def self.active - where("due_date > ? OR due_date IS NULL", Date.today) + def expired? + if due_date + due_date < Date.today + else + false + end end def participants @@ -52,4 +60,20 @@ class Milestone < ActiveRecord::Base def expires_at "expires at #{due_date.stamp("Aug 21, 2011")}" if due_date end + + def can_be_closed? + open? && issues.opened.count.zero? + end + + def is_empty? + total_items_count.zero? + end + + def open? + !closed + end + + def author_id + author_id_of_changes + end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb new file mode 100644 index 0000000000000000000000000000000000000000..8c90f5aee26cb84f8c7053009eddc1fe8623e33b --- /dev/null +++ b/app/models/namespace.rb @@ -0,0 +1,77 @@ +# == Schema Information +# +# Table name: namespaces +# +# id :integer not null, primary key +# name :string(255) not null +# path :string(255) not null +# owner_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) +# + +class Namespace < ActiveRecord::Base + attr_accessible :name, :path + + has_many :projects, dependent: :destroy + belongs_to :owner, class_name: "User" + + validates :name, presence: true, uniqueness: true + validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, + format: { with: Gitlab::Regex.path_regex, + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + validates :owner, presence: true + + delegate :name, to: :owner, allow_nil: true, prefix: true + + after_create :ensure_dir_exist + after_update :move_dir + after_destroy :rm_dir + + scope :root, where('type IS NULL') + + def self.search query + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") + end + + def self.global_id + 'GLN' + end + + def to_param + path + end + + def human_name + owner_name + end + + def ensure_dir_exist + namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path) + system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path) + end + + def move_dir + if path_changed? + old_path = File.join(Gitlab.config.gitolite.repos_path, path_was) + new_path = File.join(Gitlab.config.gitolite.repos_path, path) + if File.exists?(new_path) + raise "Already exists" + end + + if system("mv #{old_path} #{new_path}") + send_update_instructions + end + end + end + + def rm_dir + dir_path = File.join(Gitlab.config.gitolite.repos_path, path) + system("rm -rf #{dir_path}") + end + + def send_update_instructions + projects.each(&:send_move_instructions) + end +end diff --git a/app/models/note.rb b/app/models/note.rb index 60846e048726a2bf2ee6bf27f0fa01d43bd0331e..28b3879239fa8026c78bc684d0e366c00380ba70 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -20,7 +20,7 @@ require 'file_size_validator' class Note < ActiveRecord::Base attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, - :attachment, :line_code + :attachment, :line_code, :commit_id attr_accessor :notify attr_accessor :notify_author @@ -32,14 +32,17 @@ class Note < ActiveRecord::Base delegate :name, to: :project, prefix: true delegate :name, :email, to: :author, prefix: true - validates :project, presence: true - validates :note, presence: true, length: { within: 0..5000 } + validates :note, :project, presence: true validates :attachment, file_size: { maximum: 10.megabytes.to_i } - mount_uploader :attachment, AttachmentUploader + validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } + validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + + mount_uploader :attachment, AttachmentUploader # Scopes - scope :common, ->{ where(noteable_id: nil) } + scope :for_commits, ->{ where(noteable_type: "Commit") } + scope :common, ->{ where(noteable_id: nil, commit_id: nil) } scope :today, ->{ where("created_at >= :date", date: Date.today) } scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) } scope :since, ->(day) { where("created_at >= :date", date: (day)) } @@ -67,7 +70,7 @@ class Note < ActiveRecord::Base # override to return commits, which are not active record def noteable if for_commit? - project.commit(noteable_id) + project.commit(commit_id) else super end @@ -122,4 +125,12 @@ class Note < ActiveRecord::Base def downvote? note.start_with?('-1') || note.start_with?(':-1:') end + + def noteable_type_name + if noteable_type.present? + noteable_type.downcase + else + "wall" + end + end end diff --git a/app/models/project.rb b/app/models/project.rb index 3cbc9417b8f0ac9a01709e8ff496c73c0e20b7ec..3e5c912e0b47a22cc5489b9cbfb1dd53da5095e9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -9,14 +9,13 @@ # created_at :datetime not null # updated_at :datetime not null # private_flag :boolean default(TRUE), not null -# code :string(255) # owner_id :integer # default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null -# group_id :integer +# namespace_id :integer # require "grit" @@ -26,13 +25,24 @@ class Project < ActiveRecord::Base include PushObserver include Authority include Team + include NamespacedProject + + class TransferError < StandardError; end + + attr_accessible :name, :path, :description, :default_branch, :issues_enabled, + :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin] + + attr_accessible :namespace_id, :owner_id, as: :admin - attr_accessible :name, :path, :description, :code, :default_branch, :issues_enabled, - :wall_enabled, :merge_requests_enabled, :wiki_enabled attr_accessor :error_code # Relations - belongs_to :group + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" + belongs_to :namespace + + # TODO: replace owner with creator. + # With namespaces a project owner will be a namespace owner + # so this field makes sense only for global projects belongs_to :owner, class_name: "User" has_many :users, through: :users_projects has_many :events, dependent: :destroy @@ -54,36 +64,79 @@ class Project < ActiveRecord::Base # Validations validates :owner, presence: true validates :description, length: { within: 0..2000 } - validates :name, uniqueness: true, presence: true, length: { within: 0..255 } - validates :path, uniqueness: true, presence: true, length: { within: 0..255 }, - format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/, - message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } - validates :code, presence: true, uniqueness: true, length: { within: 1..255 }, - format: { with: /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/, + validates :name, presence: true, length: { within: 0..255 } + validates :path, presence: true, length: { within: 0..255 }, + format: { with: Gitlab::Regex.path_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } validates :issues_enabled, :wall_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } + + validates_uniqueness_of :name, scope: :namespace_id + validates_uniqueness_of :path, scope: :namespace_id + validate :check_limit, :repo_name # Scopes scope :public_only, where(private_flag: false) scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) } scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } + scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } + scope :personal, ->(user) { where(namespace_id: user.namespace_id) } + scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } class << self + def authorized_for user + projects = includes(:users_projects, :namespace) + projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id) + end + def active joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") end def search query - where("name LIKE :query OR code LIKE :query OR path LIKE :query", query: "%#{query}%") + where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%") + end + + def find_with_namespace(id) + if id.include?("/") + id = id.split("/") + namespace_id = Namespace.find_by_path(id.first).id + where(namespace_id: namespace_id).find_by_path(id.last) + else + where(path: id, namespace_id: nil).last + end end def create_by_user(params, user) + namespace_id = params.delete(:namespace_id) + project = Project.new params Project.transaction do + + # Parametrize path for project + # + # Ex. + # 'GitLab HQ'.parameterize => "gitlab-hq" + # + project.path = project.name.dup.parameterize + project.owner = user + + # Apply namespace if user has access to it + # else fallback to user namespace + if namespace_id != Namespace.global_id + project.namespace_id = user.namespace_id + + if namespace_id + group = Group.find_by_id(namespace_id) + if user.can? :manage_group, group + project.namespace_id = namespace_id + end + end + end + project.save! # Add user as project master @@ -126,7 +179,7 @@ class Project < ActiveRecord::Base end def repo_name - denied_paths = %w(gitolite-admin groups projects dashboard) + denied_paths = %w(gitolite-admin admin dashboard groups help profile projects search) if denied_paths.include?(path) errors.add(:path, "like #{path} is not allowed") @@ -134,11 +187,15 @@ class Project < ActiveRecord::Base end def to_param - code + if namespace + namespace.path + "/" + path + else + path + end end def web_url - [Gitlab.config.url, code].join("/") + [Gitlab.config.gitlab.url, path_with_namespace].join("/") end def common_notes @@ -146,15 +203,15 @@ class Project < ActiveRecord::Base end def build_commit_note(commit) - notes.new(noteable_id: commit.id, noteable_type: "Commit") + notes.new(commit_id: commit.id, noteable_type: "Commit") end def commit_notes(commit) - notes.where(noteable_id: commit.id, noteable_type: "Commit", line_code: nil) + notes.where(commit_id: commit.id, noteable_type: "Commit", line_code: nil) end def commit_line_notes(commit) - notes.where(noteable_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL") + notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL") end def public? @@ -173,10 +230,6 @@ class Project < ActiveRecord::Base last_event.try(:created_at) || updated_at end - def wiki_notes - Note.where(noteable_id: wikis.pluck(:id), noteable_type: 'Wiki', project_id: self.id) - end - def project_id self.id end @@ -192,4 +245,24 @@ class Project < ActiveRecord::Base def gitlab_ci? gitlab_ci_service && gitlab_ci_service.active end + + # For compatibility with old code + def code + path + end + + def items_for entity + case entity + when 'issue' then + issues + when 'merge_request' then + merge_requests + end + end + + def send_move_instructions + self.users_projects.each do |member| + Notify.project_was_moved_email(member.id).deliver + end + end end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 997c19bdb6bf273af13d08f07e62073c8174afd3..8d7eb788abb6e35f353c67ccd5efdc62d5499c22 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -22,7 +22,7 @@ class Snippet < ActiveRecord::Base belongs_to :author, class_name: "User" has_many :notes, as: :noteable, dependent: :destroy - delegate :name, :email, to: :author, prefix: true + delegate :name, :email, to: :author, prefix: true, allow_nil: true validates :author, presence: true validates :project, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 6d539c1f498b22d1044d838241c97773e98b784f..1bc070f040d935e419706749134313ec2cd738aa 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,6 +30,7 @@ # locked_at :datetime # extern_uid :string(255) # provider :string(255) +# username :string(255) # class User < ActiveRecord::Base @@ -38,33 +39,43 @@ class User < ActiveRecord::Base devise :database_authenticatable, :token_authenticatable, :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable - attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, + attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, - :extern_uid, :provider, :as => [:default, :admin] - attr_accessible :projects_limit, :as => :admin + :extern_uid, :provider, as: [:default, :admin] + attr_accessible :projects_limit, as: :admin attr_accessor :force_random_password + # Namespace for personal projects + has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy + has_many :groups, class_name: "Group", foreign_key: :owner_id + has_many :keys, dependent: :destroy has_many :projects, through: :users_projects has_many :users_projects, dependent: :destroy has_many :issues, foreign_key: :author_id, dependent: :destroy has_many :notes, foreign_key: :author_id, dependent: :destroy has_many :merge_requests, foreign_key: :author_id, dependent: :destroy - has_many :my_own_projects, class_name: "Project", foreign_key: :owner_id has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy + validates :name, presence: true validates :bio, length: { within: 0..255 } - validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider} + validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} + validates :username, presence: true, uniqueness: true, + format: { with: Gitlab::Regex.username_regex, + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } + before_validation :generate_password, on: :create before_save :ensure_authentication_token alias_attribute :private_token, :authentication_token + delegate :path, to: :namespace, allow_nil: true, prefix: true + # Scopes scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) } scope :admins, where(admin: true) diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 6231088ff792cf9f14d4c8575f230bfa5a80d40d..3d76a4df037f49e0ff9313784f708b70252a2198 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -28,6 +28,7 @@ class UsersProject < ActiveRecord::Base validates :user, presence: true validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" } + validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true validates :project, presence: true delegate :name, :email, to: :user, prefix: true diff --git a/app/observers/activity_observer.rb b/app/observers/activity_observer.rb index 48351bac667d36036f188630f16b4788cfdf0996..c188e5720ac3cb2a3e299aae747ccb01e472dee3 100644 --- a/app/observers/activity_observer.rb +++ b/app/observers/activity_observer.rb @@ -1,18 +1,27 @@ class ActivityObserver < ActiveRecord::Observer - observe :issue, :merge_request + observe :issue, :merge_request, :note, :milestone def after_create(record) - Event.create( - project: record.project, - target_id: record.id, - target_type: record.class.name, - action: Event.determine_action(record), - author_id: record.author_id - ) + event_author_id = record.author_id + + # Skip status notes + if record.kind_of?(Note) && record.note.include?("_Status changed to ") + return true + end + + if event_author_id + Event.create( + project: record.project, + target_id: record.id, + target_type: record.class.name, + action: Event.determine_action(record), + author_id: event_author_id + ) + end end def after_save(record) - if record.changed.include?("closed") + if record.changed.include?("closed") && record.author_id_of_changes Event.create( project: record.project, target_id: record.id, diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb index 62fd9bf8ac937b31e5c3ce29fb3efd3c98243304..131336be8b640384180bedfeb3e782cd10f78abe 100644 --- a/app/observers/issue_observer.rb +++ b/app/observers/issue_observer.rb @@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer def after_create(issue) if issue.assignee && issue.assignee != current_user - Notify.new_issue_email(issue.id).deliver + Notify.new_issue_email(issue.id).deliver end end @@ -14,9 +14,9 @@ class IssueObserver < ActiveRecord::Observer status = 'closed' if issue.is_being_closed? status = 'reopened' if issue.is_being_reopened? if status - Note.create_status_change_note(issue, current_user, status) - [issue.author, issue.assignee].compact.each do |recipient| - Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) + Note.create_status_change_note(issue, current_user, status) + [issue.author, issue.assignee].compact.each do |recipient| + Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver end end end diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb index 083aa7058d50048ecdc27a3b452ce21449dc719f..fe01efcaac2590240c1b4dd8a19c376b51f09570 100644 --- a/app/observers/note_observer.rb +++ b/app/observers/note_observer.rb @@ -21,7 +21,7 @@ class NoteObserver < ActiveRecord::Observer # 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 || "Wall" + noteable_type = note.noteable_type.presence || "Wall" notify_method = "note_#{noteable_type.underscore}_email".to_sym if Notify.respond_to? notify_method diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index 03a617098299c22ddcbaa97374ff4b13ae696fe9..b1c694569d7d4cd51bd2d299eb24d2810b776725 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -1,8 +1,12 @@ class ProjectObserver < ActiveRecord::Observer - def after_save(project) + def after_create(project) project.update_repository end + def after_update(project) + project.send_move_instructions if project.namespace_id_changed? + end + def after_destroy(project) log_info("Project \"#{project.name}\" was removed") diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb index 654621f7e1c9b0be102221811b97c650d8a7bd95..09b3c1d622fe9033991bb244b00f292677daf795 100644 --- a/app/observers/user_observer.rb +++ b/app/observers/user_observer.rb @@ -9,6 +9,16 @@ class UserObserver < ActiveRecord::Observer log_info("User \"#{user.name}\" (#{user.email}) was removed") end + def after_save user + if user.username_changed? + if user.namespace + user.namespace.update_attributes(path: user.username) + else + user.create_namespace!(path: user.username, name: user.name) + end + end + end + protected def log_info message diff --git a/app/roles/account.rb b/app/roles/account.rb index b80fbba0958d3f64b054fe0924eaa9c16bf8ae7c..ede12b6056d8be8d5cb23e99fa4b92b4cb100cd6 100644 --- a/app/roles/account.rb +++ b/app/roles/account.rb @@ -26,6 +26,18 @@ module Account is_admin? end + def abilities + @abilities ||= begin + abilities = Six.new + abilities << Ability + abilities + end + end + + def can? action, subject + abilities.allowed?(self, action, subject) + end + def last_activity_project projects.first end @@ -35,7 +47,7 @@ module Account end def cared_merge_requests - MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id).opened + MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) end def project_ids @@ -68,6 +80,45 @@ module Account end def projects_sorted_by_activity - projects.order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") + projects.sorted_by_activity + end + + def namespaces + namespaces = [] + + # Add user account namespace + namespaces << self.namespace if self.namespace + + # Add groups you can manage + namespaces += if admin + Group.all + else + groups.all + end + namespaces + end + + def several_namespaces? + namespaces.size > 1 + end + + def namespace_id + namespace.try :id + end + + def authorized_groups + @authorized_groups ||= begin + groups = Group.where(id: self.projects.pluck(:namespace_id)).all + groups = groups + self.groups + groups.uniq + end + end + + def authorized_projects + Project.authorized_for(self) + end + + def my_own_projects + Project.personal(self) end end diff --git a/app/roles/namespaced_project.rb b/app/roles/namespaced_project.rb new file mode 100644 index 0000000000000000000000000000000000000000..8656890a45690557413b9b6e440ef2bc2bc3e357 --- /dev/null +++ b/app/roles/namespaced_project.rb @@ -0,0 +1,59 @@ +module NamespacedProject + def transfer(new_namespace) + Project.transaction do + old_namespace = namespace + self.namespace = new_namespace + + old_dir = old_namespace.try(:path) || '' + new_dir = new_namespace.try(:path) || '' + + old_repo = if old_dir.present? + File.join(old_dir, self.path) + else + self.path + end + + if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present? + raise TransferError.new("Project with same path in target namespace already exists") + end + + Gitlab::ProjectMover.new(self, old_dir, new_dir).execute + + git_host.move_repository(old_repo, self) + + save! + end + rescue Gitlab::ProjectMover::ProjectMoveError => ex + raise TransferError.new(ex.message) + end + + def name_with_namespace + @name_with_namespace ||= begin + if namespace + namespace.human_name + " / " + name + else + name + end + end + end + + def namespace_owner + namespace.try(:owner) + end + + def chief + if namespace + namespace_owner + else + owner + end + end + + def path_with_namespace + if namespace + namespace.path + '/' + path + else + path + end + end +end diff --git a/app/roles/note_event.rb b/app/roles/note_event.rb new file mode 100644 index 0000000000000000000000000000000000000000..db4ced0c095541dc0e6df16766c9f7db2cdc86ae --- /dev/null +++ b/app/roles/note_event.rb @@ -0,0 +1,37 @@ +module NoteEvent + def note_commit_id + target.commit_id + end + + def note_short_commit_id + note_commit_id[0..8] + end + + def note_commit? + target.noteable_type == "Commit" + end + + def note_target + target.noteable + end + + def note_target_id + if note_commit? + target.commit_id + else + target.noteable_id.to_s + end + end + + def wall_note? + target.noteable_type.blank? + end + + def note_target_type + if target.noteable_type.present? + target.noteable_type.titleize + else + "Wall" + end.downcase + end +end diff --git a/app/roles/push_observer.rb b/app/roles/push_observer.rb index 2ee60646e97c4f03081f00b87143fa6c3cd3090b..dd33b6ebaee3570fc940b1f9d5827038701d7562 100644 --- a/app/roles/push_observer.rb +++ b/app/roles/push_observer.rb @@ -98,7 +98,7 @@ module PushObserver user_name: user.name, repository: { name: name, - url: web_url, + url: url_to_repo, description: description, homepage: web_url, }, @@ -114,7 +114,7 @@ module PushObserver id: commit.id, message: commit.safe_message, timestamp: commit.date.xmlschema, - url: "#{Gitlab.config.url}/#{code}/commits/#{commit.id}", + url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}", author: { name: commit.author_name, email: commit.author_email diff --git a/app/roles/repository.rb b/app/roles/repository.rb index 884681178227f78e6e93d7a565cfd9911f0fc697..78190ca96d0bc58ba2bbebbe96b4de7d4773405f 100644 --- a/app/roles/repository.rb +++ b/app/roles/repository.rb @@ -45,8 +45,22 @@ module Repository end def has_post_receive_file? - hook_file = File.join(path_to_repo, 'hooks', 'post-receive') - File.exists?(hook_file) + !!hook_file + end + + def valid_post_receive_file? + valid_hook_file == hook_file + end + + def valid_hook_file + @valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive')) + end + + def hook_file + @hook_file ||= begin + hook_path = File.join(path_to_repo, 'hooks', 'post-receive') + File.read(hook_path) if File.exists?(hook_path) + end end # Returns an Array of branch names @@ -79,11 +93,15 @@ module Repository end def url_to_repo - git_host.url_to_repo(path) + git_host.url_to_repo(path_with_namespace) end def path_to_repo - File.join(Gitlab.config.git_base_path, "#{path}.git") + File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git") + end + + def namespace_dir + namespace.try(:path) || '' end def update_repository @@ -160,12 +178,12 @@ module Repository return nil unless commit # Build file path - file_name = self.code + "-" + commit.id.to_s + ".tar.gz" - storage_path = Rails.root.join("tmp", "repositories", self.code) + file_name = self.path + "-" + commit.id.to_s + ".tar.gz" + storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace) file_path = File.join(storage_path, file_name) # Put files into a directory before archiving - prefix = self.code + "/" + prefix = self.path + "/" # Create file if not exists unless File.exists?(file_path) @@ -181,7 +199,7 @@ module Repository end def http_url_to_repo - http_url = [Gitlab.config.url, "/", path, ".git"].join('') + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') end # Check if current branch name is marked as protected in the system diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index b0b59a46fdb230a9425f7c81d188261de6dc6508..4320bda49999fdf65cf2e45b997e3011716935bb 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,5 +1,29 @@ .admin_dash.row - .span4 + .span3 + .ui-box + %h5 Projects + .data.padded + = link_to admin_projects_path do + %h1= Project.count + %hr + = link_to 'New Project', new_project_path, class: "btn small" + .span3 + .ui-box + %h5 Groups + .data.padded + = link_to admin_groups_path do + %h1= Group.count + %hr + = link_to 'New Group', new_admin_group_path, class: "btn small" + .span3 + .ui-box + %h5 Users + .data.padded + = link_to admin_users_path do + %h1= User.count + %hr + = link_to 'New User', new_admin_user_path, class: "btn small" + .span3 .ui-box %h5 Resque Workers @@ -19,32 +43,13 @@ %p %strong Resque status unknown - - .span4 - .ui-box - %h5 Projects - .data.padded - = link_to admin_projects_path do - %h1= Project.count - %hr - = link_to 'New Project', new_admin_project_path, class: "btn small" - .span4 - .ui-box - %h5 Users - .data.padded - = link_to admin_users_path do - %h1= User.count - %hr - = link_to 'New User', new_admin_user_path, class: "btn small" - - .row .span6 %h3 Latest projects %hr - @projects.each do |project| %p - = link_to project.name, [:admin, project] + = link_to project.name_with_namespace, [:admin, project] .span6 %h3 Latest users %hr diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml deleted file mode 100644 index e85cce66ba1c43b194a269e668ce1761866dc857..0000000000000000000000000000000000000000 --- a/app/views/admin/groups/_form.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -= form_for [:admin, @group] do |f| - - if @group.errors.any? - .alert-message.block-message.error - %span= @group.errors.full_messages.first - .clearfix.group_name_holder - = f.label :name do - Group name is - .input - = f.text_field :name, placeholder: "Example Group", class: "xxlarge" - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url + 'groups/' - = f.text_field :code, placeholder: "example" - - .form-actions - = f.submit 'Save group', class: "btn save-btn" diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index 9904122c789fa471abf3bd6ede5f5bed88ff1f42..901d07e74f36371212e4d97b49e5aa1a8cd5a9e1 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -1,3 +1,28 @@ -%h3.page_title Edit Group -%br -= render 'form' +%h3.page_title Rename Group +%hr += form_for [:admin, @group] do |f| + - if @group.errors.any? + .alert-message.block-message.error + %span= @group.errors.full_messages.first + .clearfix.group_name_holder + = f.label :name do + Group name is + .input + = f.text_field :name, placeholder: "Example Group", class: "xxlarge" + + + + .clearfix.group_name_holder + = f.label :path do + %span.cred Group path is + .input + = f.text_field :path, placeholder: "example-group", class: "xxlarge danger" + %ul.cred + %li Changing group path can have unintended side effects. + %li Renaming group path will rename directory for all related projects + %li It will change web url for access group and group projects. + %li It will change the git path to repositories under this group. + + .form-actions + = f.submit 'Rename group', class: "btn danger" + = link_to 'Cancel', admin_groups_path, class: "btn cancel-btn" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 6a0794cfd44ada9caae24fa9bc86866855017447..49acedc8c79979413016533633ce36a5c90d45e0 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,4 +1,3 @@ -= render 'admin/shared/projects_head' %h3.page_title Groups %small @@ -13,17 +12,24 @@ %table %thead - %th Name - %th Code - %th Projects - %th Edit - %th.cred Danger Zone! + %tr + %th + Name + %i.icon-sort-down + %th Path + %th Projects + %th Owner + %th.cred Danger Zone! - @groups.each do |group| %tr - %td= link_to group.name, [:admin, group] - %td= group.code + %td + %strong= link_to group.name, [:admin, group] + %td= group.path %td= group.projects.count - %td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small" - %td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger" + %td + = link_to group.owner_name, admin_user_path(group.owner_id) + %td.bgred + = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small" + = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger" = paginate @groups, theme: "admin" diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index d6b6ea1535e5ac5812eb75203c398fe5e15d12fc..6ff0e781d1740461cbb40d9e9b6f0e8859a9edf8 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,3 +1,21 @@ %h3.page_title New Group -%br -= render 'form' +%hr += form_for [:admin, @group] do |f| + - if @group.errors.any? + .alert-message.block-message.error + %span= @group.errors.full_messages.first + .clearfix + = f.label :name do + Group name is + .input + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" + + = f.submit 'Create group', class: "btn primary" + %hr + .padded + %ul + %li Group is kind of directory for several projects + %li All created groups are private + %li People within a group see only projects they have access to + %li All projects of group will be stored in group directory + %li You will be able to move existing projects into group diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 309a10e5bb4c473b87748b99afa12cd197658ac3..41f6d9b351696b3f71b375acd3debb9cff000fe1 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -1,9 +1,5 @@ -= render 'admin/shared/projects_head' %h3.page_title Group: #{@group.name} - = link_to edit_admin_group_path(@group), class: "btn right" do - %i.icon-edit - Edit %br %table.zebra-striped @@ -17,36 +13,82 @@ Name: %td = @group.name + + = link_to edit_admin_group_path(@group), class: "btn btn-small right" do + %i.icon-edit + Rename %tr %td %b - Code: + Path: %td - = @group.code + %span.monospace= File.join(Gitlab.config.gitolite.repos_path, @group.path) %tr %td %b Owner: %td = @group.owner_name -.ui-box - %h5 - Projects - %small - (#{@group.projects.count}) - %ul.unstyled + .right + = link_to "#", class: "btn btn-small change-owner-link" do + %i.icon-edit + Change owner + + %tr.change-owner-holder.hide + %td.bgred + %b.cred + New Owner: + %td.bgred + = form_for [:admin, @group] do |f| + = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} + %div + = f.submit 'Change Owner', class: "btn danger" + = link_to "Cancel", "#", class: "btn change-owner-cancel-link" +%fieldset + %legend Projects (#{@group.projects.count}) + %table + %thead + %tr + %th Project name + %th Path + %th Users + %th.cred Danger Zone! - @group.projects.each do |project| - %li.wll - %strong - = link_to project.name, [:admin, project] - .right - = link_to 'Remove from group', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Are you sure?', method: :delete, class: "btn danger small" - .clearfix + %tr + %td + = link_to project.name_with_namespace, [:admin, project] + %td + %span.monospace= project.path_with_namespace + ".git" + %td= project.users.count + %td.bgred + = link_to 'Transfer project to global namespace', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Remove project from group and move to global namespace. Are you sure?', method: :delete, class: "btn danger small" + -%br -%h3 Add new project -%br = form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do - = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' - .form-actions - = submit_tag 'Add', class: "btn primary" + %fieldset + %legend Move projects to group + .alert + You can move only projects with existing repos + %br + Group projects will be moved in group directory and will not be accessible by old path + .clearfix + = label_tag :project_ids do + Projects + .input + = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' + .form-actions + = submit_tag 'Add', class: "btn primary" + +:javascript + $(function(){ + var modal = $('.change-owner-holder'); + $('.change-owner-link').bind("click", function(){ + $(this).hide(); + modal.show(); + }); + $('.change-owner-cancel-link').bind("click", function(){ + modal.hide(); + $('.change-owner-link').show(); + }) + }) + diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 0efe6db7483242f2f5e516782af8ab79c27f4dbb..25644d6321ab7903760b0d52e031d2110fc463a1 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -3,12 +3,20 @@ = link_to "githost.log", "#githost", 'data-toggle' => 'tab' %li = link_to "application.log", "#application", 'data-toggle' => 'tab' + %li + = link_to "production.log", "#production", 'data-toggle' => 'tab' + +%p.light To prevent perfomance issues admin logs output the last 2000 lines .tab-content .tab-pane.active#githost .file_holder#README .file_title %i.icon-file githost.log + .right + = link_to '#', class: 'log-bottom' do + %i.icon-arrow-down + Scroll down .file_content.logs %ol - Gitlab::GitLogger.read_latest.each do |line| @@ -19,8 +27,26 @@ .file_title %i.icon-file application.log + .right + = link_to '#', class: 'log-bottom' do + %i.icon-arrow-down + Scroll down .file_content.logs %ol - Gitlab::AppLogger.read_latest.each do |line| %li %p= line + .tab-pane#production + .file_holder#README + .file_title + %i.icon-file + production.log + .right + = link_to '#', class: 'log-bottom' do + %i.icon-arrow-down + Scroll down + .file_content.logs + %ol + - Gitlab::Logger.read_latest_for('production.log').each do |line| + %li + %p= line diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml index 4848e7391a3370f7b093da3d7691f217ec954e05..27c22872d501456bc4b7fd4bfd2a31c45b0a0a53 100644 --- a/app/views/admin/projects/_form.html.haml +++ b/app/views/admin/projects/_form.html.haml @@ -11,59 +11,55 @@ .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %hr - .adv_settings - %h6 Advanced settings: + %fieldset.adv_settings + %legend Advanced settings: .clearfix = f.label :path do Path .input - .input-prepend - %strong - = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" + = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true - - unless project.new_record? + - if project.repo_exists? .clearfix - = f.label :owner_id - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} + = f.label :default_branch, "Default Branch" + .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") - - if project.repo_exists? - .clearfix - = f.label :default_branch, "Default Branch" - .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") + %fieldset.adv_settings + %legend Features: - - unless project.new_record? - %hr - .adv_settings - %h6 Features: + .clearfix + = f.label :issues_enabled, "Issues" + .input= f.check_box :issues_enabled - .clearfix - = f.label :issues_enabled, "Issues" - .input= f.check_box :issues_enabled + .clearfix + = f.label :merge_requests_enabled, "Merge Requests" + .input= f.check_box :merge_requests_enabled - .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 :wall_enabled, "Wall" - .input= f.check_box :wall_enabled + .clearfix + = f.label :wiki_enabled, "Wiki" + .input= f.check_box :wiki_enabled + + %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. - .clearfix - = f.label :wiki_enabled, "Wiki" - .input= f.check_box :wiki_enabled - - unless project.new_record? - .actions - = f.submit 'Save Project', class: "btn save-btn" - = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" + .actions + = f.submit 'Save Project', class: "btn save-btn" + = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" diff --git a/app/views/admin/projects/_new_form.html.haml b/app/views/admin/projects/_new_form.html.haml deleted file mode 100644 index d793b6f3affbc75765a89b4ded930c324a89f5a5..0000000000000000000000000000000000000000 --- a/app/views/admin/projects/_new_form.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -= form_for [:admin, @admin_project] do |f| - - if @admin_project.errors.any? - .alert-message.block-message.error - %span= @admin_project.errors.full_messages.first - .clearfix.project_name_holder - = f.label :name do - Project name is - .input - = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - = f.submit 'Create project', class: "btn primary project-submit" - - %hr - %div.adv_settings - %h6 Advanced settings: - .clearfix - = f.label :path do - Git Clone - .input - .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record? - %span.add-on= ".git" - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml index b5255671154d5b10dbaef8d38631758cd14a2674..7b59a0cc75308fe8aef1d9937cadd918d8c26ceb 100644 --- a/app/views/admin/projects/edit.html.haml +++ b/app/views/admin/projects/edit.html.haml @@ -1,3 +1,3 @@ -%h3.page_title #{@admin_project.name} → Edit project +%h3.page_title #{@project.name} → Edit project %hr -= render 'form', project: @admin_project += render 'form', project: @project diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 3335fce00789c7e821f594c5cbc39cb9b46e5476..310cfa53890fd8d0ed0dd818f2a7bb2e310de17c 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,27 +1,32 @@ -= render 'admin/shared/projects_head' %h3.page_title - Projects - = link_to 'New Project', new_admin_project_path, class: "btn small right" + Projects (#{@projects.count}) + = link_to 'New Project', new_project_path, class: "btn small right" %br = form_tag admin_projects_path, method: :get, class: 'form-inline' do + = select_tag :namespace_id, namespaces_options(params[:namespace_id], :all), class: "chosen xlarge", prompt: "Project namespace" = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" %table %thead - %th Name - %th Path - %th Team Members - %th Last Commit - %th Edit - %th.cred Danger Zone! + %tr + %th + Name + %i.icon-sort-down + %th Path + %th Team Members + %th Last Commit + %th Edit + %th.cred Danger Zone! - - @admin_projects.each do |project| + - @projects.each do |project| %tr - %td= link_to project.name, [:admin, project] - %td= project.path + %td + = link_to project.name_with_namespace, [:admin, project] + %td + %span.monospace= project.path_with_namespace + ".git" %td= project.users_projects.count %td= last_commit(project) %td= link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn small" %td.bgred= link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn small danger" -= paginate @admin_projects, theme: "admin" += paginate @projects, theme: "admin" diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml deleted file mode 100644 index 933cb671142881c0a1a347786fd41565bc3df221..0000000000000000000000000000000000000000 --- a/app/views/admin/projects/new.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -.project_new_holder - %h3.page_title - New Project - %hr - = render 'new_form' -%div.save-project-loader.hide - %center - = image_tag "ajax_loader.gif" - %h3 Creating project & repository. Please wait a few minutes - -:javascript - $(function(){ new Projects(); }); diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 78df8f2d2e95b2b1e0780e61cd88988e4d2b7121..634b1836754a026cd26a32a30e4b0745629fc1e5 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,18 +1,27 @@ -= render 'admin/shared/projects_head' %h3.page_title - Project: #{@admin_project.name} - = link_to edit_admin_project_path(@admin_project), class: "btn right" do + Project: #{@project.name_with_namespace} + = link_to edit_admin_project_path(@project), class: "btn right" do %i.icon-edit Edit -- if !@admin_project.has_post_receive_file? && @admin_project.has_commits? - %br - .alert.alert-error - %span - %strong Important! - Project has commits but missing post-receive file. - %br - If you exported project manually - copy post-receive hook to bare repository +- if @project.has_commits? + - if !@project.has_post_receive_file? + %br + .alert.alert-error + %span + %strong Project has commits but missing post-receive file. + %br + If you exported project manually - make a link of post-receive hook file from gitolite to project repository + - elsif !@project.valid_post_receive_file? + %br + .alert.alert-error + %span + %strong Project has invalid post-receive file. + %br + 1. Make sure your gitolite instace has latest post-receive file. + %br + 2. Make a link of post-receive hook file from gitolite to project repository + %br %table.zebra-striped @@ -25,36 +34,79 @@ %b Name: %td - = @admin_project.name + = @project.name + %tr + %td + %b + Namespace: + %td + - if @project.namespace + = @project.namespace.human_name + - else + Global + %tr + %td + %b + Owned by: + %td + - if @project.chief + = link_to @project.chief.name, admin_user_path(@project.chief) + - else + (deleted) %tr %td %b - Code: + Created by: %td - = @admin_project.code + = @project.owner_name || '(deleted)' %tr %td %b - Path: + Created at: %td - = @admin_project.path + = @project.created_at.stamp("March 1, 1999") + +%table.zebra-striped + %thead + %tr + %th Repository + %th %tr %td %b - Owner: + FS Path: %td - = @admin_project.owner_name || '(deleted)' + %code= @project.path_to_repo + %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 + %tr + %td + %b + Last commit at: + %td + = last_commit(@project) %tr %td %b Post Receive File: %td - = check_box_tag :post_receive_file, 1, @admin_project.has_post_receive_file?, disabled: true + = check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true + %br -%h3 +%h5 Team %small - (#{@admin_project.users_projects.count}) + (#{@project.users_projects.count}) %br %table.zebra-striped %thead @@ -64,7 +116,7 @@ %th Repository Access %th - - @admin_project.users_projects.each do |tm| + - @project.users_projects.each do |tm| %tr %td = link_to tm.user_name, admin_user_path(tm.user) @@ -73,9 +125,9 @@ %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" %br -%h3 Add new team member +%h5 Add new team member %br -= form_tag team_update_admin_project_path(@admin_project), class: "bulk_import", method: :put do += form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do %table.zebra-striped %thead %tr diff --git a/app/views/admin/shared/_projects_head.html.haml b/app/views/admin/shared/_projects_head.html.haml deleted file mode 100644 index 3f5c34c533cc5960221a29cc837de758901c3ab3..0000000000000000000000000000000000000000 --- a/app/views/admin/shared/_projects_head.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%ul.nav.nav-tabs - = nav_link(controller: :projects) do - = link_to 'Projects', admin_projects_path, class: "tab" - = nav_link(controller: :groups) do - = link_to 'Groups', admin_groups_path, class: "tab" diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 7010c2727c6085dedd892ab13e0e4f6d32934bdc..45195152cb707d3a83dbf5466f0d28b76b651523 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -6,47 +6,42 @@ - @admin_user.errors.full_messages.each do |msg| %li= msg - .row - .span7 - .ui-box - %br - .clearfix - = f.label :name - .input - = f.text_field :name - %span.help-inline * required - .clearfix - = f.label :email - .input - = f.text_field :email - %span.help-inline * required - %hr - -if f.object.new_record? - .clearfix - = f.label :force_random_password do - %span Generate random password - .input= f.check_box :force_random_password, {}, true, nil - - %div.password-fields - .clearfix - = f.label :password - .input= f.password_field :password, disabled: f.object.force_random_password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation, disabled: f.object.force_random_password - %hr - .clearfix - = f.label :skype - .input= f.text_field :skype - .clearfix - = f.label :linkedin - .input= f.text_field :linkedin - .clearfix - = f.label :twitter - .input= f.text_field :twitter - .span5 - .ui-box - %br + %fieldset + %legend Account + .clearfix + = f.label :name + .input + = f.text_field :name, required: true + %span.help-inline * required + .clearfix + = f.label :username + .input + = f.text_field :username, required: true + %span.help-inline * required + .clearfix + = f.label :email + .input + = f.text_field :email, required: true + %span.help-inline * required + + %fieldset + %legend Password + .clearfix + = f.label :password + .input= f.password_field :password, disabled: f.object.force_random_password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation, disabled: f.object.force_random_password + -if f.object.new_record? + .clearfix + = f.label :force_random_password do + %span Generate random password + .input= f.check_box :force_random_password, {}, true, nil + + %fieldset + %legend Access + .row + .span8 .clearfix = f.label :projects_limit .input= f.number_field :projects_limit @@ -55,23 +50,27 @@ = f.label :admin do %strong.cred Administrator .input= f.check_box :admin + .span4 - unless @admin_user.new_record? - %hr - .padded.cred + .alert.alert-error - if @admin_user.blocked - %span - This user is blocked and is not able to login to GitLab - .clearfix - = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right" + %p This user is blocked and is not able to login to GitLab + = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small" - else - %span - Blocked users will be removed from all projects & will not be able to login to GitLab. - .clearfix - = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small right danger" + %p Blocked users will be removed from all projects & will not be able to login to GitLab. + = link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" + %fieldset + %legend Profile + .clearfix + = f.label :skype + .input= f.text_field :skype + .clearfix + = f.label :linkedin + .input= f.text_field :linkedin + .clearfix + = f.label :twitter + .input= f.text_field :twitter - .row - .span6 - .span6 .actions = f.submit 'Save', class: "btn save-btn" - if @admin_user.new_record? diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml index 032e3cfaa996b7251a10b5333abbd6ba3afdf9ef..f8ff77b8f5313e1559182751a3ebd69118a863bb 100644 --- a/app/views/admin/users/edit.html.haml +++ b/app/views/admin/users/edit.html.haml @@ -1,3 +1,6 @@ -%h3.page_title #{@admin_user.name} → Edit user +%h3.page_title + #{@admin_user.name} → + %i.icon-edit + Edit user %hr = render 'form' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 5ef94ef5f34b267d03a711e569407d47587e7038..1df4f590bcb40169dcdbecf6b611f61f182c677a 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,12 +1,12 @@ %h3.page_title - Users + Users (#{@admin_users.count}) = link_to 'New User', new_admin_user_path, class: "btn small right" %br = form_tag admin_users_path, method: :get, class: 'form-inline' do = text_field_tag :name, params[:name], class: "xlarge" = submit_tag "Search", class: "btn submit primary" -%ul.nav.nav-pills +%ul.nav.nav-tabs %li{class: "#{'active' unless params[:filter]}"} = link_to "Active", admin_users_path %li{class: "#{'active' if params[:filter] == "admins"}"} @@ -21,26 +21,33 @@ %table %thead - %th Admin - %th Name - %th Email - %th Projects - %th Edit - %th Blocked - %th.cred Danger Zone! + %tr + %th Admin + %th + Name + %i.icon-sort-down + %th Username + %th Email + %th Projects + %th Edit + %th.cred Danger Zone! - @admin_users.each do |user| %tr %td= check_box_tag "admin", 1, user.admin, disabled: :disabled %td= link_to user.name, [:admin, user] + %td= user.username %td= user.email %td= user.users_projects.count %td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small" - %td - - if user.blocked - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" + %td.bgred + - if user == current_user + %span.cred It's you! - else - = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" - %td.bgred= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" + - if user.blocked + = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" + - else + = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" + = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger" = paginate @admin_users, theme: "admin" diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml index 70ead0d3f7d6109ec9cb4dd7b8f9e9469e0b8793..1e82b249cf1f8566492d4b2246f115b18ce95e72 100644 --- a/app/views/admin/users/new.html.haml +++ b/app/views/admin/users/new.html.haml @@ -1,3 +1,5 @@ -%h3.page_title New user -%br +%h3.page_title + %i.icon-plus + New user +%hr = render 'form' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index e73f4d10876ac4db65a6ee5ed0da92cd8d446013..852aead79e2c5415796b29d71c23a6ee8f6a3c17 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -21,6 +21,12 @@ Email: %td = @admin_user.email + %tr + %td + %b + Username: + %td + = @admin_user.username %tr %td %b @@ -31,6 +37,12 @@ %b Blocked: %td= check_box_tag "blocked", 1, @admin_user.blocked, disabled: :disabled + %tr + %td + %b + Created at: + %td + = @admin_user.created_at.stamp("March 1, 1999") %tr %td %b @@ -60,7 +72,7 @@ = @admin_user.twitter %br -%h3 Add User to Projects +%h5 Add User to Projects %br = form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do %table @@ -70,7 +82,7 @@ %th Project Access: %tr - %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3" %tr @@ -80,8 +92,22 @@ %strong= link_to "here", help_permissions_path, class: "vlink" %br +- if @admin_user.groups.present? + %h5 Owner of groups: + %br + + %table.zebra-striped + %thead + %tr + %th Name + + - @admin_user.groups.each do |group| + %tr + %td= link_to group.name, admin_group_path(group) + + - if @admin_user.projects.present? - %h3 Projects + %h5 Projects: %br %table.zebra-striped @@ -95,7 +121,7 @@ - @admin_user.users_projects.each do |tm| - project = tm.project %tr - %td= link_to project.name, admin_project_path(project) + %td= link_to project.name_with_namespace, admin_project_path(project) %td= tm.project_access_human %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" diff --git a/app/views/commit/show.patch.erb b/app/views/commit/show.patch.erb deleted file mode 100644 index ce1c3d023f538a394934c9241e94fb2216418688..0000000000000000000000000000000000000000 --- a/app/views/commit/show.patch.erb +++ /dev/null @@ -1 +0,0 @@ -<%= @commit.to_patch %> diff --git a/app/views/commits/_commit_box.html.haml b/app/views/commits/_commit_box.html.haml index 26753a1465f7b159be8a1cf7d5e1787f2ca5ac9e..8f7826e0c8db9841abdcdab6a49a196850a4db7e 100644 --- a/app/views/commits/_commit_box.html.haml +++ b/app/views/commits/_commit_box.html.haml @@ -5,9 +5,14 @@ %span.btn.disabled.grouped %i.icon-comment = @notes_count - = link_to project_commit_path(@project, @commit, format: :patch), class: "btn small grouped" do - %i.icon-download-alt - Get Patch + .left.btn-group + %a.btn.small.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + %i.icon-download-alt + Download as + %span.caret + %ul.dropdown-menu + %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch) + %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff) = link_to project_tree_path(@project, @commit), class: "browse-button primary grouped" do %strong Browse Code » %h3.commit-title.page_title diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml index c3c7d49ce748cd4f3b3b0e6a9a5166acb79345de..c9217989884ac38112a1f60ba14de2f5a3ebd331 100644 --- a/app/views/commits/_commits.html.haml +++ b/app/views/commits/_commits.html.haml @@ -3,4 +3,4 @@ %h5.small %i.icon-calendar = day.stamp("28 Aug, 2010") - %ul.unstyled= render commits + %ul.well-list= render commits diff --git a/app/views/commits/_diffs.html.haml b/app/views/commits/_diffs.html.haml index 53c2319fb38400dd692bc187c9352a1777d7ff78..e7733f0506bba399040b014b8cded90699d0760c 100644 --- a/app/views/commits/_diffs.html.haml +++ b/app/views/commits/_diffs.html.haml @@ -43,7 +43,7 @@ - if file.text? = render "commits/text_file", diff: diff, index: i - elsif file.image? - - old_file = (@commit.prev_commit.tree / diff.old_path) + - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? - if diff.renamed_file || diff.new_file || diff.deleted_file .diff_file_content_image .image{class: image_diff_class(diff)} diff --git a/app/views/compare/_form.html.haml b/app/views/compare/_form.html.haml index 07f1c818e4d731e4ce75e3af7ec50768ae206c0c..7e3a2a0e1f52fbb4640c34039d71a8dbb5f7451f 100644 --- a/app/views/compare/_form.html.haml +++ b/app/views/compare/_form.html.haml @@ -1,23 +1,30 @@ %div - %p.slead - Fill input field with commit id like - %code.label_branch 4eedf23 - or branch/tag name like - %code.label_branch master - and press compare button for commits list, code diff. + - unless params[:to] + %p.slead + Fill input field with commit id like + %code.label_branch 4eedf23 + or branch/tag name like + %code.label_branch master + and press compare button for commits list, code diff. - %br + %br = form_tag project_compare_index_path(@project), method: :post do .clearfix - = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge" - = "..." - = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" + .pull-left + - if params[:to] && params[:from] + = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} + = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge" + = "..." + = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" + .pull-left + + = submit_tag "Compare", class: "btn primary wide commits-compare-btn" - if @refs_are_same .alert %span Refs are the same - .actions - = submit_tag "Compare", class: "btn primary wide commits-compare-btn" + + :javascript $(function() { diff --git a/app/views/compare/show.html.haml b/app/views/compare/show.html.haml index 528c8b44af45346aaf53d5240f731fc9ecd85513..2abbd3fc0ee8772b8ad3f8cc8ea29519d471abff 100644 --- a/app/views/compare/show.html.haml +++ b/app/views/compare/show.html.haml @@ -9,7 +9,7 @@ - if @commits.present? %div.ui-box %h5.small Commits (#{@commits.count}) - %ul.unstyled= render @commits + %ul.well-list= render @commits - unless @diffs.empty? %h4 Diff diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..c63ef24fca58503c2c1a4e9e71137f389a3f6a44 --- /dev/null +++ b/app/views/dashboard/_activities.html.haml @@ -0,0 +1,13 @@ += render "events/event_last_push", event: @last_push + +.event_filter + = event_filter_link EventFilter.push, 'Push events' + = event_filter_link EventFilter.merged, 'Merge events' + = event_filter_link EventFilter.comments, 'Comments' + = event_filter_link EventFilter.team, 'Team' + +- if @events.any? + .content_list= render @events +- else + %p.nothing_here_message Projects activity will be displayed here +.loading.hide diff --git a/app/views/dashboard/_filter.html.haml b/app/views/dashboard/_filter.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..4624af799487e5486fa7a630a24776d7d74f4d88 --- /dev/null +++ b/app/views/dashboard/_filter.html.haml @@ -0,0 +1,33 @@ += form_tag dashboard_filter_path(entity), method: 'get' do + %fieldset.dashboard-search-filter + = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' } + = button_tag type: 'submit', class: 'btn' do + %i.icon-search + + %fieldset + %legend Status: + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if !params[:status])} + = link_to dashboard_filter_path(entity, status: nil) do + Open + %li{class: ("active" if params[:status] == 'closed')} + = link_to dashboard_filter_path(entity, status: 'closed') do + Closed + %li{class: ("active" if params[:status] == 'all')} + = link_to dashboard_filter_path(entity, status: 'all') do + All + + %fieldset + %legend Projects: + %ul.nav.nav-pills.nav-stacked + - @projects.each do |project| + - unless entities_per_project(project, entity).zero? + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to dashboard_filter_path(entity, project_id: project.id) do + = project.name_with_namespace + %small.right= entities_per_project(project, entity) + + %fieldset + %hr + = link_to "Reset", dashboard_filter_path(entity), class: 'btn right' + diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml index 7c5e9f3fab0b038f93423501a684f1f35cfa5f12..9e3401e51b818c87d743f4302181e58aae43dc37 100644 --- a/app/views/dashboard/_groups.html.haml +++ b/app/views/dashboard/_groups.html.haml @@ -8,13 +8,13 @@ = link_to new_admin_group_path, class: "btn very_small info" do %i.icon-plus New Group - %ul.unstyled + %ul.well-list - groups.each do |group| - %li.wll - = link_to group_path(id: group.code), class: dom_class(group) do - %strong.group_name= truncate(group.name, length: 25) + %li + = link_to group_path(id: group.path), class: dom_class(group) do + %strong.well-title= truncate(group.name, length: 35) %span.arrow → %span.last_activity %strong Projects: - %span= group.projects.count + %span= group.projects.authorized_for(current_user).count diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 00f19ccdcc62392115b61a0c9ef12efbad75baa7..cffafb5445c2b476018974c500f3e924dd8ee9c7 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -8,14 +8,29 @@ = link_to new_project_path, class: "btn very_small info" do %i.icon-plus New Project - %ul.unstyled + %ul.nav.nav-projects-tabs + = nav_tab :scope, nil do + = link_to "All", dashboard_path + = nav_tab :scope, 'personal' do + = link_to "Personal", dashboard_path(scope: 'personal') + = nav_tab :scope, 'joined' do + = link_to "Joined", dashboard_path(scope: 'joined') + + %ul.well-list - projects.each do |project| - %li.wll + %li = link_to project_path(project), class: dom_class(project) do - %strong.project_name= truncate(project.name, length: 25) + - if project.namespace + = project.namespace.human_name + \/ + %strong.well-title + = truncate(project.name, length: 25) %span.arrow → %span.last_activity %strong Last activity: %span= project_last_activity(project) + - if projects.blank? + %li + %h3.nothing_here_message There are no projects here. .bottom= paginate projects, theme: "gitlab" diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..ca57cd30732b0ef5b42fe1ebcd1aeac182ff0839 --- /dev/null +++ b/app/views/dashboard/_sidebar.html.haml @@ -0,0 +1,14 @@ +- if @groups.present? + = render "groups", groups: @groups += render "projects", projects: @projects +%div + %span.rss-icon + = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do + = image_tag "rss_ui.png", title: "feed" + %strong News Feed + +%hr +.gitlab-promo + = link_to "Homepage", "http://gitlabhq.com" + = link_to "Blog", "http://blog.gitlabhq.com" + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..d1676ed11fadc6d7ec51a788c413ea0db96457d0 --- /dev/null +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -0,0 +1,12 @@ +%h3.nothing_here_message + There are no projects you have access to. + %br + - if current_user.can_create_project? + You can create up to + = current_user.projects_limit + projects. Click on button below to add a new one + .link_holder + = link_to new_project_path, class: "btn primary" do + New Project » + - else + If you will be added to project - it will be displayed here diff --git a/app/views/dashboard/index.atom.builder b/app/views/dashboard/index.atom.builder index ffa15258f078ac92df971167b4a0d0530e789b61..2bb42a65bac7f512b44ae157ad0c28cfb8a36b26 100644 --- a/app/views/dashboard/index.atom.builder +++ b/app/views/dashboard/index.atom.builder @@ -7,7 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.allowed? + if event.proper? event = EventDecorator.decorate(event) xml.entry do event_link = event.feed_url diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index d0882c6dab72af3e89151829a9f565d5e520f25d..b64aa86cf73a40a72e8ab14b039ea3a96c5c232a 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -1,51 +1,11 @@ -- if @projects.any? +- if @has_authorized_projects .projects .activities.span8 - = render "events/event_last_push", event: @last_push - = render 'shared/no_ssh' - - .event_filter - = event_filter_link EventFilter.push, 'Push events' - = event_filter_link EventFilter.merged, 'Merge events' - = event_filter_link EventFilter.comments, 'Comments' - = event_filter_link EventFilter.team, 'Team' - - - if @events.any? - .content_list= render @events - - else - %p.nothing_here_message Projects activity will be displayed here - .loading.hide - .side - - if @groups.present? - = render "groups", groups: @groups - = render "projects", projects: @projects - %div - %span.rss-icon - = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do - = image_tag "rss_ui.png", title: "feed" - %strong News Feed - - %hr - .gitlab-promo - = link_to "Homepage", "http://gitlabhq.com" - = link_to "Blog", "http://blog.gitlabhq.com" - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" - + = render 'activities' + .side.span4 + = render 'sidebar' - else - %h3.nothing_here_message There are no projects you have access to. - %br - %h4.nothing_here_message - - if current_user.can_create_project? - You can create up to - = current_user.projects_limit - projects. Click on button below to add a new one - .link_holder - = link_to new_project_path, class: "btn primary" do - New Project » - - else - If you will be added to project - it will be displayed here - - + = render "zero_authorized_projects" :javascript $(function(){ Pager.init(20); }); diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 5bd07bcd89ff2352c63d43feb9c3d71cdad6e495..28bdc5ed814e8083c300f63d9c2d9d45403f3f11 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "#{@user.name} issues" - xml.link :href => dashboard_issues_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => dashboard_issues_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" - xml.id dashboard_issues_url(:private_token => @user.private_token) + xml.title "#{current_user.name} issues" + xml.link :href => dashboard_issues_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" + xml.link :href => dashboard_issues_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" + xml.id dashboard_issues_url(:private_token => current_user.private_token) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index cc488d57e9e9b63d8c1829547997ed7b20707c18..52863229644b040148a625074f1039a0dd8ea1e7 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -3,17 +3,21 @@ %small (assigned to you) %small.right #{@issues.total_count} issues -%br -.clearfix -- if @issues.any? - - @issues.group_by(&:project).each do |group| - %div.ui-box - - @project = group[0] - %h5= @project.name - %ul.unstyled.issues_table - - group[1].each do |issue| - = render(partial: 'issues/show', locals: {issue: issue}) - %hr - = paginate @issues, theme: "gitlab" -- else - %h3.nothing_here_message Nothing to show here +%hr + +.row + .span3 + = render 'filter', entity: 'issue' + .span9 + - if @issues.any? + - @issues.group_by(&:project).each do |group| + %div.ui-box + - @project = group[0] + %h5= link_to_project @project + %ul.well-list.issues_table + - group[1].each do |issue| + = render(partial: 'issues/show', locals: {issue: issue}) + %hr + = paginate @issues, theme: "gitlab" + - else + %p.nothing_here_message Nothing to show here diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 23a7e7222d7ccc340e9372723b6684a57881b7ee..ea7c8c9a3d514270a02cce99fd799e779d59d653 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -3,16 +3,21 @@ %small (authored by or assigned to you) %small.right #{@merge_requests.total_count} merge requests -%br -- if @merge_requests.any? - - @merge_requests.group_by(&:project).each do |group| - %ul.unstyled.ui-box - - @project = group[0] - %h5= @project.name - - group[1].each do |merge_request| - = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) - %hr - = paginate @merge_requests, theme: "gitlab" +%hr +.row + .span3 + = render 'filter', entity: 'merge_request' + .span9 + - if @merge_requests.any? + - @merge_requests.group_by(&:project).each do |group| + .ui-box + - @project = group[0] + %h5= link_to_project @project + %ul.well-list + - group[1].each do |merge_request| + = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) + %hr + = paginate @merge_requests, theme: "gitlab" -- else - %h3.nothing_here_message Nothing to show here + - else + %h3.nothing_here_message Nothing to show here diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 38192d7107d16af0238248d009a1baaf0f3da85d..474e7ef746c5061f323eb3a4525b5077edbd6edd 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -3,7 +3,7 @@ - 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 :email, :class => "text top", :placeholder => "Email" + = 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 diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index 3b60ed8b0ee355751d1d3ef7be6956f4ec3363a9..f2d082cb77defd275ead59eba0b1ac71cd9e5905 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,4 +1,5 @@ -%h1 Access Denied +%h1.http_status_code 403 +%h3.page_title Access Denied %hr -%h2 You are not allowed to access this page. +%p You are not allowed to access this page. %p Read more about project permissions #{link_to "here", help_permissions_path, class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index d7b5e68e870db0516324a8a29d0271a56793e57d..a0aa63064896171ef874e73c7bb3561379a49456 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,3 +1,4 @@ -%h1 Encoding Error +%h1.http_status_code 500 +%h3.page_title Encoding Error %hr %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index cd01ea1b0e65d80510f6ccdd27c8caf2878a2fa9..5c9c4953284f1dc7db2291b4e0985d673baee9ca 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,6 +1,6 @@ -%h1 404 +%h1.http_status_code 404 +%h3.page_title Git Resource Not found %hr -%h2 Git Resource Not found %p Application can't get access to some branch or commit in your repository. It may have been moved. diff --git a/app/views/errors/gitolite.html.haml b/app/views/errors/gitolite.html.haml index 699e6984db6357472d3b3edd92ee3aa7f1f66527..590bca71dd42a4d42d91d01135c45fdc596ad745 100644 --- a/app/views/errors/gitolite.html.haml +++ b/app/views/errors/gitolite.html.haml @@ -1,6 +1,6 @@ -%h1 Git Error +%h1.http_status_code 500 +%h3.page_title GitLab was unable to access your Gitolite system. %hr -%h2 GitLab was unable to access your Gitolite system. .git_error_tips %h4 Tips for Administrator: @@ -21,5 +21,5 @@ Permissions: %pre = preserve do - sudo chmod -R 770 #{Gitlab.config.git_base_path} - sudo chown -R git:git #{Gitlab.config.git_base_path} + sudo chown -R git:git #{Gitlab.config.gitolite.repos_path} + sudo chmod -R ug+rwXs #{Gitlab.config.gitolite.repos_path} diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index a4e8d0204a9858372587213f6ec1b2c81c366b44..ee23d2197b4760d7589f48c50dbac54d49be9a37 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,4 +1,4 @@ -%h1 404 +%h1.http_status_code 404 +%h3.page_title The resource you were looking for doesn't exist. %hr -%h2 The resource you were looking for doesn't exist. %p You may have mistyped the address or the page may have moved. diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 0d91a67a60daecf13d93d88bedfac749f98c4e9a..191aed0747e0b3c39cb1bf775a6683bf72401523 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,15 +1,15 @@ -- if event.allowed? +- if event.proper? %div.event-item - = event_image(event) - = image_tag gravatar_icon(event.author_email), class: "avatar" + %span.cgray.right + #{time_ago_in_words(event.created_at)} ago. + + = image_tag gravatar_icon(event.author_email), class: "avatar s24" - if event.push? = render "events/event/push", event: event + .clearfix + - elsif event.note? + = render "events/event/note", event: event - else = render "events/event/common", event: event - .clearfix - %span.cgray.right - = time_ago_in_words(event.created_at) - ago. - .clearfix diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index e15f1ac063cacc8dbc45c9d40a469f1cc7b8c170..b2376019c8eb772651396589c83ca02f5bd04599 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -6,7 +6,7 @@ = link_to project_commits_path(event.project, event.ref_name) do %strong= truncate(event.ref_name, length: 28) at - %strong= link_to event.project.name, event.project + %strong= link_to_project event.project %span = time_ago_in_words(event.created_at) ago. diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..8c12969345f9800824bffb8402f068f10190023e --- /dev/null +++ b/app/views/events/event/_note.html.haml @@ -0,0 +1,25 @@ +.event-title + %span.author_name= link_to_author event + %span.event_label commented on #{event.note_target_type} + - if event.note_target + - if event.note_commit? + = link_to event.note_short_commit_id, project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" + - else + = link_to [event.project, event.note_target] do + %strong= truncate event.note_target_id + + - elsif event.wall_note? + -# nothing here + - else + %strong (deleted) + at + - if event.project + = link_to_project event.project + - else + = event.project_name + +.event-body + %span.hint + + %i.icon-comment + = truncate event.target.note, length: 70 diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 869321ed6997038479066800e71c537133768824..119b8e828d0d9bea9c9937cc5c3a18f8db27a822 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -7,12 +7,12 @@ = link_to project_commits_path(event.project, event.ref_name) do %strong= event.ref_name at - %strong= link_to event.project.name, event.project + %strong= link_to_project event.project - if event.push_with_commits? - project = event.project .event-body - %ul.unstyled.event_commits + %ul.well-list.event_commits - few_commits = event.commits[0...2] - few_commits.each do |commit| = render "events/commit", commit: commit, project: project diff --git a/app/views/groups/_new_member.html.haml b/app/views/groups/_new_member.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..f48c2c23d83b9d520104dae8f273bd591493df11 --- /dev/null +++ b/app/views/groups/_new_member.html.haml @@ -0,0 +1,18 @@ += form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| + %fieldset + %legend= "New Team member(s) for #{@project.name}" + + %h6 1. Choose people you want in the team + .clearfix + = f.label :user_ids, "People" + .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) + + %h6 2. Set access level for them + .clearfix + = f.label :project_access, "Project Access" + .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" + + .form-actions + = hidden_field_tag :redirect_to, people_group_path(@group, project_id: @project.id) + = f.submit 'Add', class: "btn save-btn" + diff --git a/app/views/groups/_people_filter.html.haml b/app/views/groups/_people_filter.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..79a1b01a5faad5763ce877525c3b2a0b39598461 --- /dev/null +++ b/app/views/groups/_people_filter.html.haml @@ -0,0 +1,14 @@ += form_tag people_group_path(@group), method: 'get' do + %fieldset + %legend Projects: + %ul.nav.nav-pills.nav-stacked + - @projects.each do |project| + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to people_group_path(@group, project_id: project.id) do + = project.name_with_namespace + %small.right= project.users.count + + %fieldset + %hr + = link_to "Reset", people_group_path(@group), class: 'btn right' + diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index b565dad37a94099c60799aea5e282202381f5c6c..0b491879fe04f581dc6883d33cef1dddac31fce7 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -3,11 +3,18 @@ Projects %small (#{projects.count}) - %ul.unstyled + - if can? current_user, :manage_group, @group + %span.right + = link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do + %i.icon-plus + New Project + %ul.well-list + - if projects.blank? + %p.nothing_here_message This groups has no projects yet - projects.each do |project| - %li.wll + %li = link_to project_path(project), class: dom_class(project) do - %strong.project_name= truncate(project.name, length: 25) + %strong.well-title= truncate(project.name, length: 25) %span.arrow → %span.last_activity diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index cc488d57e9e9b63d8c1829547997ed7b20707c18..0daf4d752a8469b2f2a3304c888df9f725fbf29d 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -10,7 +10,7 @@ %div.ui-box - @project = group[0] %h5= @project.name - %ul.unstyled.issues_table + %ul.well-list.issues_table - group[1].each do |issue| = render(partial: 'issues/show', locals: {issue: issue}) %hr diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 23a7e7222d7ccc340e9372723b6684a57881b7ee..72aa4ad11e109f78f48370fdbcea393cda326abd 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -6,7 +6,7 @@ %br - if @merge_requests.any? - @merge_requests.group_by(&:project).each do |group| - %ul.unstyled.ui-box + %ul.well-list.ui-box - @project = group[0] %h5= @project.name - group[1].each do |merge_request| diff --git a/app/views/groups/people.html.haml b/app/views/groups/people.html.haml index 258108089c3495412ffb15b7a4a38d62f99829f1..be3dd7a4d7872cd048dff8ab9b46e1333643ef9f 100644 --- a/app/views/groups/people.html.haml +++ b/app/views/groups/people.html.haml @@ -1,12 +1,20 @@ -.ui-box - %h5 - People - %small - (#{@users.size}) - %ul.unstyled - - @users.each do |user| - %li.wll - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" - %strong= user.name - %span.cgray= user.email +.row + .span3 + = render 'people_filter' + .span9 + - if @project && can?(current_user, :manage_group, @group) + = render "new_member" + .ui-box + %h5 + Team + %small + (#{@users.size}) + %ul.well-list + - @users.each do |user| + %li + = image_tag gravatar_icon(user.email, 16), class: "avatar s16" + %strong= user.name + %span.cgray= user.email + - if @group.owner == user + %span.btn.btn-small.disabled.right Group Owner diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index fa3bfade28b07f8eccbceb5e03be25cfb95b860f..9aa52ea55930c5b5498f542acd1b38f499892947 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -7,7 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.allowed? + if event.proper? event = EventDecorator.decorate(event) xml.entry do event_link = event.feed_url diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 72d7ad9a5920fd2574f27eae8533f619e38e3201..76bc2639d61e0a4ab47d1167269363e722032f4a 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -6,13 +6,12 @@ %span.cgray Events and projects are filtered in scope of group %hr - = render 'shared/no_ssh' - if @events.any? .content_list= render @events - else - %h4.nothing_here_message Projects activity will be displayed here + %p.nothing_here_message Projects activity will be displayed here .loading.hide - .side + .side.span4 = render "projects", projects: @projects %div %span.rss-icon diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml index 00085166bcf93f3a8661e6f9c1aa2fe769d8e7f3..3f16637dd2e3538b96b6a41e4fc1ae394fc4d21f 100644 --- a/app/views/help/api.html.haml +++ b/app/views/help/api.html.haml @@ -21,6 +21,8 @@ = link_to "Issues", "#issues", 'data-toggle' => 'tab' %li = link_to "Milestones", "#milestones", 'data-toggle' => 'tab' + %li + = link_to "Notes", "#notes", 'data-toggle' => 'tab' .tab-content .tab-pane.active#README @@ -94,3 +96,12 @@ .file_content.wiki = preserve do = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) + + .tab-pane#notes + .file_holder + .file_title + %i.icon-file + Notes + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "api", "notes.md")) diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 962f2175126e9616ae8cdfa180a515cbe6c3bd54..ebd499c05e026fe04956851f788c804e9262902a 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -34,3 +34,6 @@ %li %h5= link_to "SSH keys", help_ssh_path + + %li + %h5= link_to "GitLab Rake Tasks", help_raketasks_path diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index f9287fa09965f57fce53859830695193e77c5a77..c9ec701add7ef223e1ec30888451c1ec22ad94ad 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -4,61 +4,66 @@ ← to index %hr -.row - .ui-box.span2 - %h5 Guest - %ul.unstyled - %li Create new issue - %li Leave comments - %li Write on project wall +%fieldset + %legend Guest + %ul + %li Create new issue + %li Leave comments + %li Write on project wall - .ui-box.span3 - %h5 Reporter - %ul.unstyled - %li Create new issue - %li Leave comments - %li Write on project wall - %li Pull project code - %li Download project - %li Create new merge request - %li Create a code snippets +%fieldset + %legend Reporter + %ul + %li Create new issue + %li Leave comments + %li Write on project wall + %li Pull project code + %li Download project + %li Create new merge request + %li Create a code snippets - .ui-box.span3 - %h5 Developer - %ul.unstyled - %li Create new issue - %li Leave comments - %li Write on project wall - %li Pull project code - %li Download project - %li Create new merge request - %li Create a code snippets - %li Create new branches - %li Push to non-protected branches - %li Remove non-protected branches - %li Add tags - %li Write a wiki +%fieldset + %legend Developer + %ul + %li Create new issue + %li Leave comments + %li Write on project wall + %li Pull project code + %li Download project + %li Create new merge request + %li Create a code snippets + %li Create new branches + %li Push to non-protected branches + %li Remove non-protected branches + %li Add tags + %li Write a wiki - .ui-box.span3 - %h5 Master - %ul.unstyled - %li Create new issue - %li Leave comments - %li Write on project wall - %li Pull project code - %li Download project - %li Create new merge request - %li Create a code snippets - %li Create new branches - %li Push to non-protected branches - %li Remove non-protected branches - %li Add tags - %li Write a wiki - %li Add new team members - %li Push to protected branches - %li Remove protected branches - %li Push with force option - %li Edit project - %li Add Deploy Keys to project - %li Configure Project Hooks +%fieldset + %legend Master + %ul + %li Create new issue + %li Leave comments + %li Write on project wall + %li Pull project code + %li Download project + %li Create new merge request + %li Create a code snippets + %li Create new branches + %li Push to non-protected branches + %li Remove non-protected branches + %li Add tags + %li Write a wiki + %li Add new team members + %li Push to protected branches + %li Remove protected branches + %li Push with force option + %li Edit project + %li Add Deploy Keys to project + %li Configure Project Hooks + +%fieldset + %legend Owner + %ul + %li Transfer project to another namespace + %li Remove project diff --git a/app/views/help/raketasks.html.haml b/app/views/help/raketasks.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..62cfa1521a77e37ed3662cef298be9f21e10eaa8 --- /dev/null +++ b/app/views/help/raketasks.html.haml @@ -0,0 +1,55 @@ +%h3.page_title GitLab Rake Tasks +.back_link + = link_to help_path do + ← to index +%hr + +%p.slead + GitLab provides some specific rake tasks to enable special features or perform maintenance tasks. + +%ul.nav.nav-tabs.log-tabs + %li.active + = link_to "Features", "#features", 'data-toggle' => 'tab' + %li + = link_to "Maintenance", "#maintenance", 'data-toggle' => 'tab' + %li + = link_to "User Management", "#user_management", 'data-toggle' => 'tab' + %li + = link_to "Backup & Restore", "#backup_restore", 'data-toggle' => 'tab' + +.tab-content + .tab-pane.active#features + .file_holder + .file_title + %i.icon-file + Features + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "raketasks", "features.md")) + + .tab-pane#maintenance + .file_holder + .file_title + %i.icon-file + Maintenance + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "raketasks", "maintenance.md")) + + .tab-pane#user_management + .file_holder + .file_title + %i.icon-file + User Management + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "raketasks", "user_management.md")) + + .tab-pane#backup_restore + .file_holder + .file_title + %i.icon-file + Backup & Restore + .file_content.wiki + = preserve do + = markdown File.read(Rails.root.join("doc", "raketasks", "backup_restore.md")) diff --git a/app/views/hooks/_data_ex.html.erb b/app/views/hooks/_data_ex.html.erb index 7dd6b9e0750ea5c02e09721d1cc071b2f8b423e5..246a8235517543172623a8450c5c9fc356fd859e 100644 --- a/app/views/hooks/_data_ex.html.erb +++ b/app/views/hooks/_data_ex.html.erb @@ -7,10 +7,9 @@ :user_name => "John Smith", :repository => { :name => "Diaspora", - :url => "localhost/diaspora", + :url => "git@localhost:diaspora.git", :description => "", - :homepage => "localhost/diaspora", - :private => true + :homepage => "http://localhost/diaspora", }, :commits => [ [0] { diff --git a/app/views/hooks/index.html.haml b/app/views/hooks/index.html.haml index 1b59c8e81aba0fe4f5d0270533bce8a7cc586727..6a36c749123012737c82e8d2b825f0e61a318e3a 100644 --- a/app/views/hooks/index.html.haml +++ b/app/views/hooks/index.html.haml @@ -22,22 +22,21 @@ %hr -if @hooks.any? - %h3 - Hooks - %small (#{@hooks.count}) + %h3.page_title + Hooks (#{@hooks.count}) %br %table %thead %tr %th URL - %th Method %th - @hooks.each do |hook| %tr %td + %span.badge.badge-info POST = link_to project_hook_path(@project, hook) do %strong= hook.url - = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn small right" - %td POST %td - = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "danger btn small right" + .right + = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn small grouped" + = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "danger btn small grouped" diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 670b4e059f4fe87f84fce15bef2fe02641ddd152..030f797c088101d01f73f054e569cd4a8c60d1d6 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -1,18 +1,18 @@ %div.issue-form-holder %h3.page_title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.id}" - = form_for [@project, @issue], remote: request.xhr? do |f| + = form_for [@project, @issue] do |f| -if @issue.errors.any? .alert-message.block-message.error - %ul - - @issue.errors.full_messages.each do |msg| - %li= msg + - @issue.errors.full_messages.each do |msg| + %span= msg + %br .issue_form_box .issue_title .clearfix = f.label :title do %strong= "Subject *" .input - = f.text_field :title, maxlength: 255, class: "xxlarge js-gfm-input", autofocus: true + = f.text_field :title, maxlength: 255, class: "xxlarge js-gfm-input", autofocus: true, required: true .issue_middle_block .issue_assignee = f.label :assignee_id do @@ -47,11 +47,38 @@ -else = f.submit 'Save changes', class: "save-btn btn" - - cancel_class = 'btn cancel-btn' - - if request.xhr? - = link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class - - else - - if @issue.new_record? - = link_to "Cancel", project_issues_path(@project), class: cancel_class - - else - = link_to "Cancel", project_issue_path(@project, @issue), class: cancel_class + - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue) + = link_to "Cancel", cancel_path, class: 'btn cancel-btn' + + + + +:javascript + $(function(){ + $("#issue_label_list") + .bind( "keydown", function( event ) { + if ( event.keyCode === $.ui.keyCode.TAB && + $( this ).data( "autocomplete" ).menu.active ) { + event.preventDefault(); + } + }) + .autocomplete({ + minLength: 0, + source: function( request, response ) { + response( $.ui.autocomplete.filter( + #{raw labels_autocomplete_source}, extractLast( request.term ) ) ); + }, + focus: function() { + return false; + }, + select: function(event, ui) { + var terms = split( this.value ); + terms.pop(); + terms.push( ui.item.value ); + terms.push( "" ); + this.value = terms.join( ", " ); + return false; + } + }); + }); + diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml index f82ae8bde589c3c80ccd5f5643c45709a1f14105..d7ba4300ce77e85c10321a81cc906a386cf616f2 100644 --- a/app/views/issues/_issues.html.haml +++ b/app/views/issues/_issues.html.haml @@ -6,7 +6,7 @@ .row .span7= paginate @issues, remote: true, theme: "gitlab" .span3.right - %span.cgray.right + %span.cgray.right %span.issue_counter #{@issues.total_count} issues for this filter - else diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml index 8aa92ebfd6aca017535e7a0c5d0143d40ab8ad04..4641e8bdc6351179dc95b6a44cc0e7e673bff8a3 100644 --- a/app/views/issues/_show.html.haml +++ b/app/views/issues/_show.html.haml @@ -1,4 +1,4 @@ -%li.wll{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) } +%li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) } - if controller.controller_name == 'issues' .issue_check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) @@ -16,7 +16,7 @@ = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small grouped reopen_issue", remote: true - else = link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small grouped close_issue", remote: true - = link_to edit_project_issue_path(issue.project, issue), class: "btn small edit-issue-link grouped", remote: true do + = link_to edit_project_issue_path(issue.project, issue), class: "btn small edit-issue-link grouped" do %i.icon-edit Edit @@ -28,7 +28,7 @@ %p= link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title" %span.update-author - %small.cdark= "##{issue.id}" + %span.cdark= "##{issue.id}" - if issue.assignee assigned to #{issue.assignee_name} - else diff --git a/app/views/issues/create.js.haml b/app/views/issues/create.js.haml deleted file mode 100644 index d90cbf0d30c0d135c542fe299e5c358f22d30734..0000000000000000000000000000000000000000 --- a/app/views/issues/create.js.haml +++ /dev/null @@ -1,10 +0,0 @@ -- if @issue.valid? - :plain - switchFromNewIssue(); - $("#issues-table").prepend("#{escape_javascript(render(partial: 'show', locals: {issue: @issue}))}"); - $.ajax({type: "GET", url: location.href, dataType: "script"}); -- else - :plain - $("#new_issue_dialog").empty(); - $("#new_issue_dialog").append("#{escape_javascript(render('form'))}"); - $('select#issue_assignee_id').chosen(); diff --git a/app/views/issues/edit.js.haml b/app/views/issues/edit.js.haml deleted file mode 100644 index a994572f9b9f5c0e6adb77a8ca3c158aa1bf33a9..0000000000000000000000000000000000000000 --- a/app/views/issues/edit.js.haml +++ /dev/null @@ -1,4 +0,0 @@ -:plain - $("#edit_issue_dialog").html("#{escape_javascript(render('form'))}"); - switchToEditIssue(); - diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index d89b183d36028afaae34194ee59ac4629d9b851e..08d4393b201001d1948cb64a52bf00fca211e23a 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -6,7 +6,7 @@ .right .span5 - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true, id: "new_issue_link" do + = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "right btn", title: "New Issue", id: "new_issue_link" do %i.icon-plus New Issue = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do @@ -27,7 +27,7 @@ .left = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") - = select_tag('update[milestone_id]', options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), prompt: "Milestone") + = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :f, params[:f] = button_tag "Save", class: "btn update_selected_issues" @@ -51,16 +51,13 @@ = form_tag project_issues_path(@project), method: :get, class: :right do = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), prompt: "Labels") = select_tag(:assignee_id, options_from_collection_for_select([unassigned_filter] + @project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") - = select_tag(:milestone_id, options_from_collection_for_select([unassigned_filter] + @project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), prompt: "Milestone") + = select_tag(:milestone_id, options_from_collection_for_select([unassigned_filter] + issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") = hidden_field_tag :f, params[:f] .clearfix - %ul#issues-table.unstyled.issues_table + %ul#issues-table.well-list.issues_table = render "issues" -#new_issue_dialog -#edit_issue_dialog - :javascript $(function(){ issuesPage(); diff --git a/app/views/issues/new.js.haml b/app/views/issues/new.js.haml deleted file mode 100644 index 4cbcc563e285ab7ec7f1e9795d5795f7259c42c9..0000000000000000000000000000000000000000 --- a/app/views/issues/new.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $("#new_issue_dialog").html("#{escape_javascript(render('form'))}"); - switchToNewIssue(); diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index 9114febdd0267766a848e0af2233d246f33bb575..1d4d6a13c2bb33efa7c780e02d307623e95b81e2 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -26,22 +26,16 @@ .main_box .top_box_content - %h4 + %h4.box-title - if @issue.closed - .alert-message.error.status_info Closed - - else - .alert-message.success.status_info Open + .error.status_info Closed = gfm escape_once(@issue.title) .middle_box_content - %cite.cgray Created by - = image_tag gravatar_icon(@issue.author_email), width: 16, class: "lil_av" - %strong.author= link_to_issue_author(@issue) - - - if @issue.assignee - %cite.cgray and currently assigned to - = image_tag gravatar_icon(@issue.assignee_email), width: 16, class: "lil_av" - %strong.author= link_to_issue_assignee(@issue) + %cite.cgray + Created by #{link_to_member(@project, @issue.author)} + - if @issue.assignee + \ and currently assigned to #{link_to_member(@project, @issue.assignee)} - if @issue.milestone - milestone = @issue.milestone diff --git a/app/views/issues/update.js.haml b/app/views/issues/update.js.haml index 44722895025f4cd56dc0117ad045b824ebc25278..7f66022a2de34d851856dd0ba4ad9e1ff5f9f549 100644 --- a/app/views/issues/update.js.haml +++ b/app/views/issues/update.js.haml @@ -2,13 +2,3 @@ - if @issue.valid? :plain $("##{dom_id(@issue)}").fadeOut(); -- else - - if @issue.valid? - :plain - updatePage(); - switchFromEditIssue(); - - else - :plain - $("#edit_issue_dialog").empty(); - $("#edit_issue_dialog").append("#{escape_javascript(render('form'))}"); - $('select#issue_assignee_id').chosen(); diff --git a/app/views/kaminari/admin/_gap.html.haml b/app/views/kaminari/admin/_gap.html.haml index f82f185ac35a5460e36cc5f48693efc979c18558..3ffd12f8587aa72b3929dbdcbfbaf999383111b6 100644 --- a/app/views/kaminari/admin/_gap.html.haml +++ b/app/views/kaminari/admin/_gap.html.haml @@ -4,5 +4,6 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.page.gap - = raw(t 'views.pagination.truncate') +%li{class: "page"} + %span.page.gap + = raw(t 'views.pagination.truncate') diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml index 8a465a9e02cb8433f9dc1b62f7dd133912f1011c..6e223e8e61d7f7b641f854da854a118d4d25dd76 100644 --- a/app/views/labels/_label.html.haml +++ b/app/views/labels/_label.html.haml @@ -1,4 +1,4 @@ -%li.wll +%li %strong %i.icon-tag = label.name diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml index 4e41d375d6abcb9dd022d104552cd3626274c90d..6eb2c00e56d28f4b4e181c45cad3497346ee8447 100644 --- a/app/views/labels/index.html.haml +++ b/app/views/labels/index.html.haml @@ -4,7 +4,7 @@ Labels %br %div.ui-box - %ul.unstyled.labels-table + %ul.well-list.labels-table - @labels.each do |label| = render 'label', label: label diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 86564ad7110fa7266737c838ddf60288c3145052..9961ce8dd349dcff22d89c02566863a32aac31bc 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,17 +1,3 @@ -- if alert || notice - - text = alert || notice - %div{style: "display:none", id: "flash_container"} - %center - %h4= text - :javascript - $(function(){ - $("#flash_container").slideDown("slow"); - $("#flash_container").click(function(){ - $(this).slideUp("slow"); - }); - setTimeout("hideFlash()",3000); - }); - - function hideFlash(){ - $("#flash_container").slideUp("slow"); - } +- if text = alert || notice + #flash-container + %h4= text diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 25fe9d806bc356af3599bc431a2fc50050192d38..4a0f60d36c26b5f5eb3a02d44a7da39bacaae04b 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -2,7 +2,7 @@ %meta{charset: "utf-8"} %title GitLab - = " > #{@project.name}" if @project && !@project.new_record? + = " > #{title}" if defined?(title) = favicon_link_tag 'favicon.ico' = stylesheet_link_tag "application" = javascript_include_tag "application" diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 38e1d7f05978cfdc97c642b0d5bf2f3f2c0ff78f..8fbec43f4a19dbc8b025d964c7bbc48a28c363e6 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -18,7 +18,7 @@ %li = link_to profile_path, title: "Your Profile", class: 'has_bottom_tooltip', 'data-original-title' => 'Your profile' do %i.icon-user - %span.separator + %li.separator %li = render "layouts/search" %li diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 502f289ec05bf4ea426c7518c12cc56023df0a46..8f8c7d8885e793641d7e31ddbc06415095b420e8 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,6 +1,6 @@ :javascript $(function() { - GitLab.GfmAutoComplete.Members.url = "#{ "/api/v2/projects/#{@project.code}/members" if @project }"; + GitLab.GfmAutoComplete.Members.url = "#{ "/api/v3/projects/#{@project.id}/members" if @project }"; GitLab.GfmAutoComplete.Members.params.private_token = "#{current_user.private_token}"; GitLab.GfmAutoComplete.Emoji.data = #{raw emoji_autocomplete_source}; diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 582f86ba32c66f62f6aadc7b145efe9e804d0e16..6b643ec8ccbf67c3b98c9198cca498c88aa384f9 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Admin area" %body{class: "#{app_theme} admin"} = render "layouts/flash" = render "layouts/head_panel", title: "Admin area" @@ -8,8 +8,10 @@ %ul.main_menu = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to "Stats", admin_root_path - = nav_link(controller: [:projects, :groups]) do + = nav_link(controller: :projects) do = link_to "Projects", admin_projects_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 diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 40f4f88cbce4ddea50017bb79c80db26365c0d5c..a197de387493f669787d9773d96108fe12fd0945 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Dashboard" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "Dashboard" @@ -15,7 +15,7 @@ = nav_link(path: 'dashboard#merge_requests') do = link_to dashboard_merge_requests_path do Merge Requests - %span.count= current_user.cared_merge_requests.count + %span.count= current_user.cared_merge_requests.opened.count = nav_link(path: 'search#show') do = link_to "Search", search_path = nav_link(path: 'help#index') do diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 70c4f007ba1c0e5e2983d9463ea89017f4d447d9..36c6b4c6c35d8617e1ee94811c66ed2a8e1e3bd6 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -2,5 +2,5 @@ %html{ lang: "en"} = render "layouts/head" %body.ui_basic.login-page - = render partial: "layouts/flash" + = render "layouts/flash" .container= yield diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 1f5c03bdcedede8e896d599b8a59258c8711638c..3554d88f10c0fd64092f374171af383ec5d4d90c 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Error" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 985200e253923af5c5eab034593a5354cd1a38d3..d40d9525bb89d42edac9cb70cff303e6822382c0 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,6 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "#{@group.name}" %body{class: "#{app_theme} application"} = render "layouts/flash" = render "layouts/head_panel", title: "#{@group.name}" diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 7b79897b653f85e26079db4cda305799a6ff8644..c418e1dbc68e30a80fad5c9aff905fde024bcc4a 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -3,14 +3,7 @@ %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"} %title GitLab - :css - .header h1 {color: #BBBBBB !important; font: bold 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;} - .header p {color: #c6c6c6; font: normal 12px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 18px;} - .content h2 {color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; } - .content p {color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif;} - .content a {color: #0eb6ce; text-decoration: none;} - .footer p {font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;} - .footer a {color: #0eb6ce; text-decoration: none;} + %body{bgcolor: "#EAEAEA", style: "margin: 0; padding: 0; background: #EAEAEA"} %table{align: "center", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 35px 0; background: #EAEAEA;", width: "100%"} %tr @@ -19,11 +12,11 @@ %tr %td{style: "font-size: 0px;", width: "20"} \Â - %td{align: "left", style: "padding: 18px 0 10px;", width: "580"} - %h1{style: "color: #BBBBBB; font: normal 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;"} + %td{align: "left", style: "padding: 10px 0", width: "580"} + %h1{style: "font-size: 24px; color: #BBBBBB; font: normal 22px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 32px;"} GITLAB - if @project - | #{@project.name} + → #{@project.name_with_namespace} %table{align: "center", bgcolor: "#fff", border: "0", cellpadding: "0", cellspacing: "0", style: "font-family: Helvetica, Arial, sans-serif; background: #fff;", width: "600"} %tr= yield %tr @@ -35,5 +28,5 @@ %p{style: "font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;"} You're receiving this notification because you are a member of the - if @project - #{@project.name} + #{@project.name_with_namespace} project team. diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 7a54bb7cf2f04332e02019dcbd80e571d33ef8b7..7852ed6f0e1380a88df9124abd0d064c56fae1c1 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,22 +1,22 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: "Profile" %body{class: "#{app_theme} profile"} = render "layouts/flash" = render "layouts/head_panel", title: "Profile" .container %ul.main_menu - = nav_link(path: 'profile#show', html_options: {class: 'home'}) do + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to "Profile", profile_path - = nav_link(path: 'profile#account') do - = link_to "Account", profile_account_path + = nav_link(path: 'profiles#account') do + = link_to "Account", account_profile_path = nav_link(controller: :keys) do = link_to keys_path do SSH Keys %span.count= current_user.keys.count - = nav_link(path: 'profile#design') do - = link_to "Design", profile_design_path - = nav_link(path: 'profile#history') do - = link_to "History", profile_history_path + = nav_link(path: 'profiles#design') do + = link_to "Design", design_profile_path + = nav_link(path: 'profiles#history') do + = link_to "History", history_profile_path .content= yield diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml index b1dbe41ce651c29d94ebb568cf2981e9d997b111..709807456c8701f44c8eca009cb4b5e7b6f5a97e 100644 --- a/app/views/layouts/project_resource.html.haml +++ b/app/views/layouts/project_resource.html.haml @@ -1,13 +1,15 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" + = render "layouts/head", title: @project.name_with_namespace %body{class: "#{app_theme} project"} = render "layouts/flash" - = render "layouts/head_panel", title: @project.name + = render "layouts/head_panel", title: project_title(@project) + - if can?(current_user, :download_code, @project) + = render 'shared/no_ssh' .container %ul.main_menu = nav_link(html_options: {class: "home #{project_tab_class}"}) do - = link_to @project.code, project_path(@project), title: "Project" + = link_to @project.path, project_path(@project), title: "Project" - if @project.repo_exists? - if can? current_user, :download_code, @project diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index 302e75cfb007bb6249e4b7009a8aabcd7a806d02..9606e2e53b3a69ef2ad884845b5bbd74667a1ea0 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -32,7 +32,7 @@ .top_box_content = f.label :title do %strong= "Title *" - .input= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5 + .input= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5, required: true .merge_requests_middle_box .merge_requests_assignee = f.label :assignee_id do diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml index 4f68c5f2620deea4c8bda22eaca7e541598b79ac..7369f3dd0619f67baae0e2572cf7b74d512aedcf 100644 --- a/app/views/merge_requests/_merge_request.html.haml +++ b/app/views/merge_requests/_merge_request.html.haml @@ -1,4 +1,4 @@ -%li.wll{ class: mr_css_classes(merge_request) } +%li{ class: mr_css_classes(merge_request) } .right .left - if merge_request.merged? diff --git a/app/views/merge_requests/_show.html.haml b/app/views/merge_requests/_show.html.haml index f1d0c8aaafbbbcaa1551452c5bfdcfd837d7f721..20ba991e79a41ab7a2393229ad58bda99e00aed7 100644 --- a/app/views/merge_requests/_show.html.haml +++ b/app/views/merge_requests/_show.html.haml @@ -2,6 +2,8 @@ = render "merge_requests/show/how_to_merge" = render "merge_requests/show/mr_box" = render "merge_requests/show/mr_accept" +- if @project.gitlab_ci? + = render "merge_requests/show/mr_ci" = render "merge_requests/show/commits" - if @commits.present? @@ -28,6 +30,8 @@ MergeRequest.init({ url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"}, + url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", + ci_enable: #{@project.gitlab_ci? ? "true" : "false"}, current_state: "#{@merge_request.human_state}", action: "#{controller.action_name}" }); diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml index 7bcb7a81e1ac4990b5d7339b84aa19512a17ea28..5b234bfbe02a4609c455287ad0c9cf54b644c942 100644 --- a/app/views/merge_requests/index.html.haml +++ b/app/views/merge_requests/index.html.haml @@ -30,7 +30,7 @@ = hidden_field_tag :f, params[:f] .clearfix - %ul.unstyled + %ul.well-list = render @merge_requests - if @merge_requests.blank? %li diff --git a/app/views/merge_requests/show/_commits.html.haml b/app/views/merge_requests/show/_commits.html.haml index d25e707c64e9e797a457114f02f5340addebe0c2..796922776d9c6ff7870563d88e571bb71723135b 100644 --- a/app/views/merge_requests/show/_commits.html.haml +++ b/app/views/merge_requests/show/_commits.html.haml @@ -5,19 +5,19 @@ Commits (#{@commits.count}) .merge-request-commits - if @commits.count > 8 - %ul.first_mr_commits.unstyled + %ul.first_mr_commits.well-list - @commits.first(8).each do |commit| = render "commits/commit", commit: commit %li.bottom 8 of #{@commits.count} commits displayed. %strong %a.mr_show_all_commits Click here to show all - %ul.all_mr_commits.hide.unstyled + %ul.all_mr_commits.hide.well-list - @commits.each do |commit| = render "commits/commit", commit: commit - else - %ul.unstyled + %ul.well-list - @commits.each do |commit| = render "commits/commit", commit: commit diff --git a/app/views/merge_requests/show/_diffs.html.haml b/app/views/merge_requests/show/_diffs.html.haml index 7685090311a6b26b59303888098483d3ecf705b1..0807454c4b02d9298a13f90959c3446096e0fe92 100644 --- a/app/views/merge_requests/show/_diffs.html.haml +++ b/app/views/merge_requests/show/_diffs.html.haml @@ -1,8 +1,10 @@ - if @merge_request.valid_diffs? = render "commits/diffs", diffs: @diffs - elsif @merge_request.broken_diffs? - %h4.nothing_here_message + %h4.nothing_here_message Can't load diff. - You can #{link_to "download MR patch", raw_project_merge_request_path(@project, @merge_request), class: "vlink"} instead. + You can + = link_to "download it", project_merge_request_path(@project, @merge_request), format: :diff, class: "vlink" + instead. - else %h4.nothing_here_message Nothing to merge diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml index b4b4be2980ab4f8bc26e5cb9d1991506d2a6e3fc..cd33732d191733208f2ff516b6496c855a9c7b6e 100644 --- a/app/views/merge_requests/show/_mr_box.html.haml +++ b/app/views/merge_requests/show/_mr_box.html.haml @@ -1,25 +1,20 @@ .main_box .top_box_content - %h4 - - if @merge_request.closed - .alert-message.error.status_info Closed - - else - .alert-message.success.status_info Open + %h4.box-title + - if @merge_request.merged + .error.status_info + %i.icon-ok + Merged + - elsif @merge_request.closed + .error.status_info Closed = gfm escape_once(@merge_request.title) - - if @project.gitlab_ci? - .right - = image_tag ci_status_path, class: 'status-badge' .middle_box_content %div - %cite.cgray Created at #{@merge_request.created_at.stamp("Aug 21, 2011")} by - = image_tag gravatar_icon(@merge_request.author_email), width: 16, class: "lil_av" - %strong.author= link_to_merge_request_author(@merge_request) - - - if @merge_request.assignee - %cite.cgray , currently assigned to - = image_tag gravatar_icon(@merge_request.assignee_email), width: 16, class: "lil_av" - %strong.author= link_to_merge_request_assignee(@merge_request) + %cite.cgray + Created at #{@merge_request.created_at.stamp("Aug 21, 2011")} by #{link_to_member(@project, @merge_request.author)} + - if @merge_request.assignee + \, currently assigned to #{link_to_member(@project, @merge_request.assignee)} - if @merge_request.milestone - milestone = @merge_request.milestone %cite.cgray and attached to milestone @@ -30,10 +25,10 @@ .bottom_box_content - if @merge_request.merged? %span - Merged by #{@merge_request.merge_event.author_name} + Merged by #{link_to_member(@project, @merge_request.merge_event.author)} %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago. - elsif @merge_request.closed_event %span - Closed by #{@merge_request.closed_event.author_name} + Closed by #{link_to_member(@project, @merge_request.closed_event.author)} %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. diff --git a/app/views/merge_requests/show/_mr_ci.html.haml b/app/views/merge_requests/show/_mr_ci.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..d46b606ef7f8b4cfa125be781adee638faa212b4 --- /dev/null +++ b/app/views/merge_requests/show/_mr_ci.html.haml @@ -0,0 +1,35 @@ +- if @merge_request.open? && @commits.any? + .ci_widget.ci-success{style: "display:none"} + .alert.alert-success + %i.icon-ok + %strong CI build passed + for #{@merge_request.last_commit_short_sha}. + = link_to "Build page", ci_build_details_path(@merge_request) + + + .ci_widget.ci-failed{style: "display:none"} + .alert.alert-error + %i.icon-remove + %strong CI build failed + for #{@merge_request.last_commit_short_sha}. + = link_to "Build page", ci_build_details_path(@merge_request) + + - [:running, :pending].each do |status| + .ci_widget{class: "ci-#{status}", style: "display:none"} + .alert + %i.icon-time + %strong CI build #{status} + for #{@merge_request.last_commit_short_sha}. + = link_to "Build page", ci_build_details_path(@merge_request) + + .ci_widget + .alert-message + %strong + %i.icon-refresh + Checking for CI status for #{@merge_request.last_commit_short_sha} + + .ci_widget.ci-error{style: "display:none"} + .alert.alert-error + %i.icon-remove + %strong Cannot connect to CI server. Please check your setting + diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml index 8708469cc5deb1c1f47730f5841c529b5c554870..c2ffe8e3770f1c89ab60b18f9d9222a64729f801 100644 --- a/app/views/merge_requests/show/_mr_title.html.haml +++ b/app/views/merge_requests/show/_mr_title.html.haml @@ -6,16 +6,16 @@ %span.label_branch= @merge_request.target_branch %span.right - - if @merge_request.merged? - %span.btn.small.disabled.grouped - %strong - %i.icon-ok - = "MERGED" - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - = link_to raw_project_merge_request_path(@project, @merge_request), class: "btn grouped" do - %i.icon-download-alt - Get Patch + .left.btn-group + %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } + %i.icon-download-alt + Download as + %span.caret + %ul.dropdown-menu + %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) + %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close merge request" diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml index 194eac7783ce34067f49b80c4217dbc37e044a34..1c496a93e54137a29e7d2748629cd95a634d825e 100644 --- a/app/views/milestones/_form.html.haml +++ b/app/views/milestones/_form.html.haml @@ -45,5 +45,5 @@ $( ".datepicker" ).datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } - }); + }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); }); diff --git a/app/views/milestones/_milestone.html.haml b/app/views/milestones/_milestone.html.haml index 7c4c0e67d7ce2f58f080e9062f6fc4e3712dec40..3864792f7e81995db1246d4a3964208dd1f21839 100644 --- a/app/views/milestones/_milestone.html.haml +++ b/app/views/milestones/_milestone.html.haml @@ -1,22 +1,27 @@ -%li{class: "milestone", id: dom_id(milestone) } +%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) } .right - - if can? current_user, :admin_milestone, milestone.project + - if can?(current_user, :admin_milestone, milestone.project) and milestone.open? = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn small edit-milestone-link grouped" do %i.icon-edit Edit %h4 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) + - if milestone.expired? and not milestone.closed + %span.cred (Expired) %small = milestone.expires_at - .row - .span4 - .progress.progress-info - .bar{style: "width: #{milestone.percent_complete}%;"} - .span6 - = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do - = pluralize milestone.issues.count, 'Issue' - - = link_to project_merge_requests_path(milestone.project, milestone_id: milestone.id) do - = pluralize milestone.merge_requests.count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete + - if milestone.is_empty? + %span.muted Empty + - else + .row + .span4 + .progress.progress-info + .bar{style: "width: #{milestone.percent_complete}%;"} + .span6 + = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do + = pluralize milestone.issues.count, 'Issue' + + = link_to project_merge_requests_path(milestone.project, milestone_id: milestone.id) do + = pluralize milestone.merge_requests.count, 'Merge Request' + + %span.light #{milestone.percent_complete}% complete diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml index c5333b08fdcf388a74a4555af5b551bcdcef13ae..3089595fe0b004298305882a4350f2d91a21fd12 100644 --- a/app/views/milestones/index.html.haml +++ b/app/views/milestones/index.html.haml @@ -11,15 +11,18 @@ %li{class: ("active" if (params[:f] == "active" || !params[:f]))} = link_to project_milestones_path(@project, f: "active") do Active + %li{class: ("active" if params[:f] == "closed")} + = link_to project_milestones_path(@project, f: "closed") do + Closed %li{class: ("active" if params[:f] == "all")} = link_to project_milestones_path(@project, f: "all") do All - %ul.unstyled + %ul.well-list = render @milestones - if @milestones.present? - %li.bottom= paginate @milestones, remote: true, theme: "gitlab" + %li.bottom= paginate @milestones, theme: "gitlab" - else %li %h3.nothing_here_message Nothing to show here diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml index b8bc788c953610b6d83d6be3de0fd08ba175df6c..c4975c72ef299c051570c5bab3cf6b4db7735196 100644 --- a/app/views/milestones/show.html.haml +++ b/app/views/milestones/show.html.haml @@ -1,31 +1,41 @@ -%h3.page_title - Milestone ##{@milestone.id} - %small - = @milestone.expires_at +.row + .span6 + %h3.page_title + Milestone ##{@milestone.id} + %small + = @milestone.expires_at + .back_link + = link_to project_milestones_path(@project) do + ← To milestones list + .span6 + .right + - unless @milestone.closed + = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn small grouped", title: "New Issue" do + %i.icon-plus + New Issue + = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link small grouped" + - if can?(current_user, :admin_milestone, @project) + = link_to edit_project_milestone_path(@project, @milestone), class: "btn small grouped" do + %i.icon-edit + Edit - %span.right - = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn small grouped", title: "New Issue" do - %i.icon-plus - New Issue - = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link small grouped" - - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: "btn small grouped" do - %i.icon-edit - Edit -.back_link - = link_to project_milestones_path(@project) do - ← To milestones list + +- if @milestone.can_be_closed? + %hr + %p + %span All issues for this milestone are closed. You may close milestone now. + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn small danger" .main_box .top_box_content - %h5 + %h4.box-title - if @milestone.closed - .alert-message.error.status_info Closed - - else - .alert-message.success.status_info Open + .error.status_info Closed + - elsif @milestone.expired? + .error.status_info Expired + = gfm escape_once(@milestone.title) - %small.right= @milestone.expires_at .middle_box_content %h5 @@ -34,6 +44,7 @@ #{@milestone.closed_items_count} closed – #{@milestone.open_items_count} open + %span.right= @milestone.expires_at .progress.progress-info .bar{style: "width: #{@milestone.percent_complete}%;"} @@ -43,14 +54,16 @@ = preserve do = markdown @milestone.description + .row .span6 %table.milestone-issue-filter %thead - %th - %ul.nav.nav-pills - %li.active= link_to('Open Issues', '#') - %li=link_to('All Issues', '#') + %tr + %th + %ul.nav.nav-pills + %li.active= link_to('Open Issues', '#') + %li=link_to('All Issues', '#') - @issues.each do |issue| %tr{data: {closed: issue.closed}} %td @@ -62,10 +75,11 @@ .span6 %table.milestone-merge-requests-filter %thead - %th - %ul.nav.nav-pills - %li.active= link_to('Open Merge Requests', '#') - %li=link_to('All Merge Requests', '#') + %tr + %th + %ul.nav.nav-pills + %li.active= link_to('Open Merge Requests', '#') + %li=link_to('All Merge Requests', '#') - @merge_requests.each do |merge_request| %tr{data: {closed: merge_request.closed}} %td diff --git a/app/views/notes/_common_form.html.haml b/app/views/notes/_common_form.html.haml index 0725082d504503d0e141a4a25753cf50cfa72f52..d76be75bc273a43ef7b90d104b3213845c3c62e6 100644 --- a/app/views/notes/_common_form.html.haml +++ b/app/views/notes/_common_form.html.haml @@ -7,6 +7,7 @@ %div= msg = f.hidden_field :noteable_id + = f.hidden_field :commit_id = f.hidden_field :noteable_type = f.text_area :note, size: 255, class: 'note-text js-gfm-input' #preview-note.preview_note.hide diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml index c8d79850162c2340d1cf7b87f5e7e77910ad76e2..ff80ad4e0d52d9fd279b70154f6afb29fc9a2413 100644 --- a/app/views/notes/_per_line_form.html.haml +++ b/app/views/notes/_per_line_form.html.haml @@ -11,6 +11,7 @@ %div= msg = f.hidden_field :noteable_id + = f.hidden_field :commit_id = f.hidden_field :noteable_type = f.hidden_field :line_code = f.text_area :note, size: 255, class: 'line-note-text js-gfm-input' diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml index 59130f79d6cbbf2e6b90dec237b0b15bdcf5bd0f..c433e80c9e5e5846b0b01b6ec84e606581fe8069 100644 --- a/app/views/notify/issue_status_changed_email.html.haml +++ b/app/views/notify/issue_status_changed_email.html.haml @@ -9,7 +9,7 @@ %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "} = "Issue ##{@issue.id}" = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title %br diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index 654d6cd12bebf4db5756346645ef0c6da12360a6..fba4b8654870fd848abc566cd52c46b9aaf61ead 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -9,7 +9,7 @@ %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "} = "Issue ##{@issue.id}" = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title %br diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 151aac451fb78c27ced8b7390e245807347d7ed9..9819767011eaf038d8033d8d707dc5f7bf84ea3f 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -5,7 +5,8 @@ %td{align: "left", style: "padding: 20px 0 0;"} %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} = "New Merge Request !#{@merge_request.id}" - = link_to_gfm truncate(@merge_request.title, length: 16), project_merge_request_url(@merge_request.project, @merge_request) + %p{style: "color:#646464 !important; line-height: 26px; font-size: 16px; font-family: Helvetica, Arial, sans-serif; "} + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} diff --git a/app/views/notify/note_wiki_email.html.haml b/app/views/notify/note_wiki_email.html.haml deleted file mode 100644 index 41a237fc53e1288150dcd7262e8207dbe73c63b8..0000000000000000000000000000000000000000 --- a/app/views/notify/note_wiki_email.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} - %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} - %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} - New comment for Wiki page - = link_to_gfm @wiki.title, project_wiki_url(@wiki.project, @wiki, anchor: "note_#{@note.id}") - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{style: "padding: 15px 0 15px;", valign: "top"} - %p{style: "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "} - %a{href: "#", style: "color: #0eb6ce; text-decoration: none;"} #{@note.author_name} - commented on Wiki page: - %br - %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"} - %tr - %td{valign: "top"} - %div{ style: "background:#f5f5f5; padding:20px;border:1px solid #ddd" } - = markdown(@note.note) - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml index 72b3f0658fc5dfe9c31b6d37aaad45edff1ac5b0..11117bf0b3399ef6b46d6addeec1579ebac8fa8f 100644 --- a/app/views/notify/project_access_granted_email.html.haml +++ b/app/views/notify/project_access_granted_email.html.haml @@ -1,14 +1,15 @@ %td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} + %td{width: "21"} + %td + %h2{style: "color:#646464;" } = "You have been granted #{@users_project.project_access_human} access to project" %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %tr - %td{style: "font-size: 1px; line-height: 1px;", width: "21"} - %td{align: "left", style: "padding: 20px 0 0;"} - %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} - = link_to_gfm truncate(@project.name, length: 45), project_url(@project), title: @project.name + %td{width: "21"} + %td + %h3 + = link_to project_url(@project) do + = @project.name_with_namespace %br diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..222bd0fecea4aee700e35733a72ea101469dcf32 --- /dev/null +++ b/app/views/notify/project_was_moved_email.html.haml @@ -0,0 +1,25 @@ +%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"} + %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #555; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"} + %tr + %td{width: "21"} + %td + %h2 + = "Project was moved to another location" + %td{width: "21"} + %tr + %td{width: "21"} + %td + %p + The project is now located under + = link_to project_url(@project) do + = @project.name_with_namespace + %p + To update the remote url in your local repository run: + %br + %table{border: "0", cellpadding: "0", cellspacing: "0", width: "558"} + %tr + %td{valign: "top"} + %p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" } + git remote set-url origin #{@project.ssh_url_to_repo} + %br + %td{ width: "21"} diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml index c7896af3a548fd3f8a9172e62f955ce4c2050ec2..31a5d23242cd3777c6cc588370295b358ca55e90 100644 --- a/app/views/notify/reassigned_issue_email.html.haml +++ b/app/views/notify/reassigned_issue_email.html.haml @@ -5,7 +5,7 @@ %td{align: "left", style: "padding: 20px 0 0;"} %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} = "Reassigned Issue ##{@issue.id}" - = link_to_gfm truncate(@issue.title, length: 16), project_issue_url(@issue.project, @issue) + = link_to_gfm truncate(@issue.title, length: 30), project_issue_url(@issue.project, @issue) %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml index e49b783635c846f6c6186c42617b24729bdc7897..8f7308b3dba56bbcbfbc158f11c5f1429f304011 100644 --- a/app/views/notify/reassigned_merge_request_email.html.haml +++ b/app/views/notify/reassigned_merge_request_email.html.haml @@ -5,7 +5,7 @@ %td{align: "left", style: "padding: 20px 0 0;"} %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "} = "Reassigned Merge Request !#{@merge_request.id}" - = link_to_gfm truncate(@merge_request.title, length: 16), project_merge_request_url(@merge_request.project, @merge_request) + = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.project, @merge_request) %td{style: "font-size: 1px; line-height: 1px;", width: "21"} %tr %td{style: "font-size: 1px; line-height: 1px;", width: "21"} diff --git a/app/views/profile/index.html.haml b/app/views/profile/index.html.haml deleted file mode 100644 index 84174ac5be707cdca75b99477786686636d10d81..0000000000000000000000000000000000000000 --- a/app/views/profile/index.html.haml +++ /dev/null @@ -1 +0,0 @@ -%h1 Profile diff --git a/app/views/profile/account.html.haml b/app/views/profiles/account.html.haml similarity index 55% rename from app/views/profile/account.html.haml rename to app/views/profiles/account.html.haml index 1e3a8b1a0d4a0ba9c6349c8a229fa84ea1b54ea4..3c290948d6ca5339cc19da69ea168a9dc759073c 100644 --- a/app/views/profile/account.html.haml +++ b/app/views/profiles/account.html.haml @@ -1,4 +1,4 @@ -- if Gitlab.config.omniauth_enabled? +- if Gitlab.config.omniauth.enabled %fieldset %legend Social Accounts .oauth_select_holder @@ -8,13 +8,14 @@ = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + %fieldset %legend Private token %span.cred.right keep it secret! .padded - = form_for @user, url: profile_reset_private_token_path, method: :put do |f| + = form_for @user, url: reset_private_token_profile_path, method: :put do |f| .data %p.slead Private token used to access application resources without authentication. @@ -30,7 +31,7 @@ %fieldset %legend Password - = form_for @user, url: profile_password_path, method: :put do |f| + = form_for @user, url: update_password_profile_path, method: :put do |f| .padded %p.slead After successful password update you will be redirected to login page where you should login with new password -if @user.errors.any? @@ -41,14 +42,39 @@ .clearfix = f.label :password - .input= f.password_field :password + .input= f.password_field :password, required: true .clearfix = f.label :password_confirmation - .input= f.password_field :password_confirmation - .actions - = f.submit 'Save', class: "btn save-btn" + .input + = f.password_field :password_confirmation, required: true + .clearfix + .input + = f.submit 'Save password', class: "btn save-btn" +%fieldset.update-username + %legend + Username + %small.cred.right + Changing your username can have unintended side effects! + = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| + .padded + = f.label :username + .input + = f.text_field :username, required: true + + %span.loading-gif.hide= image_tag "ajax_loader.gif" + %span.update-success.cgreen.hide + %i.icon-ok + Saved + %span.update-failed.cred.hide + %i.icon-ok + Failed + %ul.cred + %li It will change web url for personal projects. + %li It will change the git path to repositories for personal projects. + .input + = f.submit 'Save username', class: "btn save-btn" diff --git a/app/views/profile/design.html.haml b/app/views/profiles/design.html.haml similarity index 93% rename from app/views/profile/design.html.haml rename to app/views/profiles/design.html.haml index 502cca42f2d722e35819464704b24f0712b40c71..f4b50677203c027ec9ed948435fcf7c96a070413 100644 --- a/app/views/profile/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -1,4 +1,4 @@ -= form_for @user, url: profile_update_path, remote: true, method: :put do |f| += form_for @user, url: profile_path, remote: true, method: :put do |f| %fieldset.application-theme %legend Application theme diff --git a/app/views/profile/history.html.haml b/app/views/profiles/history.html.haml similarity index 100% rename from app/views/profile/history.html.haml rename to app/views/profiles/history.html.haml diff --git a/app/views/profile/show.html.haml b/app/views/profiles/show.html.haml similarity index 88% rename from app/views/profile/show.html.haml rename to app/views/profiles/show.html.haml index 7d9e90cf3f870f63647847987f90074cab00a143..934c1fdf7c4870c500ebbbd7a2e4dacfd6acb70c 100644 --- a/app/views/profile/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -8,7 +8,7 @@ %hr -= form_for @user, url: profile_update_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| += form_for @user, url: profile_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| -if @user.errors.any? %div.alert-message.block-message.error %ul @@ -33,15 +33,15 @@ %ul %li %p You can change your password on Account page - -unless Gitlab.config.disable_gravatar? + - if Gitlab.config.gravatar.enabled %li %p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} - - if Gitlab.config.omniauth_enabled? && @user.provider? + - if Gitlab.config.omniauth.enabled && @user.provider? %li - %p.hint + %p You can login through #{@user.provider.titleize}! - = link_to "click here to change", profile_account_path + = link_to "click here to change", account_profile_path .row .span7 diff --git a/app/views/profile/update.js.erb b/app/views/profiles/update.js.erb similarity index 100% rename from app/views/profile/update.js.erb rename to app/views/profiles/update.js.erb diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml new file mode 100644 index 0000000000000000000000000000000000000000..abd90269c93832e71bc0d79ccf2ab0eef56c6e42 --- /dev/null +++ b/app/views/profiles/update_username.js.haml @@ -0,0 +1,6 @@ +- if @user.valid? + :plain + $('.update-username .update-success').show(); +- else + :plain + $('.update-username .update-failed').show(); diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml index 461f4feaf61b9225f6aa7c0a9735122aaeb47e7e..2962ad980b3bfd69ffc450f6afe8a4a02277cf81 100644 --- a/app/views/projects/_clone_panel.html.haml +++ b/app/views/projects/_clone_panel.html.haml @@ -6,12 +6,12 @@ .right - unless @project.empty_repo? - if can? current_user, :download_code, @project - = link_to archive_project_repository_path(@project), class: "btn grouped" do + = link_to archive_project_repository_path(@project), class: "btn-small btn grouped" do %i.icon-download-alt Download - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn grouped" do + = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn-small btn grouped" do Merge Request - if @project.issues_enabled && can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), title: "New Issue", class: "btn grouped" do + = link_to new_project_issue_path(@project), title: "New Issue", class: "btn-small btn grouped" do Issue diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index 9ee65942fe9c92b51deb7064f0fc32f6f22183e4..7044d1f20be1183c446de80268903f3018b7646e 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -9,48 +9,62 @@ Project name is .input = f.text_field :name, placeholder: "Example Project", class: "xxlarge" - %fieldset %legend Advanced settings: - .clearfix + .control-group = f.label :path do - Path - .input - .input-prepend - %strong - = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true - .clearfix - = f.label :code do - URL - .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" - - - unless @project.new_record? || @project.heads.empty? + Repository + .controls + = text_field_tag :ppath, @project.path_to_repo, class: "xxlarge", readonly: true + + + - unless @project.heads.empty? .clearfix = f.label :default_branch, "Default Branch" .input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;") - - unless @project.new_record? - %fieldset - %legend Features: + %fieldset.features + %legend Features: - .clearfix - = f.label :issues_enabled, "Issues" - .input= f.check_box :issues_enabled + .control-group + = f.label :issues_enabled, "Issues", class: 'control-label' + .controls + = f.check_box :issues_enabled + %span.descr Lightweight issue tracking system for this project - .clearfix - = f.label :merge_requests_enabled, "Merge Requests" - .input= f.check_box :merge_requests_enabled + .control-group + = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' + .controls + = f.check_box :merge_requests_enabled + %span.descr Submit changes to be merged upstream. - .clearfix - = f.label :wall_enabled, "Wall" - .input= f.check_box :wall_enabled + .control-group + = f.label :wall_enabled, "Wall", class: 'control-label' + .controls + = f.check_box :wall_enabled + %span.descr Simple chat system for broadcasting inside project + + .control-group + = f.label :wiki_enabled, "Wiki", class: 'control-label' + .controls + = f.check_box :wiki_enabled + %span.descr Pages for project documentation + + + - if can? current_user, :change_namespace, @project + %fieldset.features + %legend Transfer: + .control-group + = f.label :namespace_id do + %span Namespace + .controls + = f.select :namespace_id, namespaces_options(@project.namespace_id || Namespace::global_id), {prompt: 'Choose a project namespace'}, {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. - .clearfix - = f.label :wiki_enabled, "Wiki" - .input= f.check_box :wiki_enabled %br @@ -58,5 +72,6 @@ = f.submit 'Save', class: "btn save-btn" = link_to 'Cancel', @project, class: "btn" - unless @project.new_record? - .right - = link_to 'Remove', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger" + - if can?(current_user, :remove_project, @project) + .right + = link_to 'Remove', @project, confirm: 'Removed project can not be restored! Are you sure?', method: :delete, class: "btn danger" diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml index e6d5e93fca76985a48cf171089913181ec52bccc..2ef29cb0c65d7fc6fee9059961e646c409a980a7 100644 --- a/app/views/projects/_new_form.html.haml +++ b/app/views/projects/_new_form.html.haml @@ -9,21 +9,12 @@ = f.text_field :name, placeholder: "Example Project", class: "xxlarge" = f.submit 'Create project', class: "btn primary project-submit" - %hr - %div.adv_settings - %h6 Advanced settings: - .clearfix - = f.label :path do - Git Clone - .input - .input-prepend - %span.add-on= Gitlab.config.ssh_path - = f.text_field :path, placeholder: "example_project", disabled: !@project.new_record? - %span.add-on= ".git" + - if current_user.several_namespaces? .clearfix - = f.label :code do - URL + = f.label :namespace_id do + %span.cgray Namespace .input - .input-prepend - %span.add-on= web_app_url - = f.text_field :code, placeholder: "example" + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'} + %hr + %p.padded + All created project are private. You choose who can see project and commit to repository. diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml index ce73fe0cf013d872563082457e8e28bd43defb0f..d388988676d216e386be6a59e5647ba351b2f409 100644 --- a/app/views/projects/create.js.haml +++ b/app/views/projects/create.js.haml @@ -9,3 +9,4 @@ $('.project_new_holder').show(); $("#new_project").replaceWith("#{escape_javascript(render('new_form'))}"); $('.save-project-loader').hide(); + new Projects(); diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index d9a151fc706e19eecc5275cb654280f89648f58b..52dff687a3a70a775401632d35011c316e4077c3 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,32 +1,34 @@ -= render 'shared/no_ssh' = render 'clone_panel' %div.git-empty - %h4 Git global setup: - %pre.dark - = preserve do - git config --global user.name "#{current_user.name}" - git config --global user.email "#{current_user.email}" + %fieldset + %legend Git global setup: + %pre.dark + = preserve do + git config --global user.name "#{current_user.name}" + git config --global user.email "#{current_user.email}" - %h4.prepend-top-20 Create Repository - %pre.dark - = preserve do - mkdir #{@project.path} - cd #{@project.path} - git init - touch README - git add README - git commit -m 'first commit' - git remote add origin #{@project.url_to_repo} - git push -u origin master + %fieldset + %legend Create Repository + %pre.dark + = preserve do + mkdir #{@project.path} + cd #{@project.path} + git init + touch README + git add README + git commit -m 'first commit' + git remote add origin #{@project.url_to_repo} + git push -u origin master - %h4.prepend-top-20 Existing Git Repo? - %pre.dark - = preserve do - cd existing_git_repo - git remote add origin #{@project.url_to_repo} - git push -u origin master + %fieldset + %legend Existing Git Repo? + %pre.dark + = preserve do + cd existing_git_repo + git remote add origin #{@project.url_to_repo} + git push -u origin master - - if can? current_user, :admin_project, @project + - if can? current_user, :remove_project, @project .prepend-top-20 = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right" diff --git a/app/views/projects/files.html.haml b/app/views/projects/files.html.haml index 9f7efcdc51573bf827b357fdc99de5b9e56e7fbb..d108308318e6a8c05b9c325926fdd3da5f34645d 100644 --- a/app/views/projects/files.html.haml +++ b/app/views/projects/files.html.haml @@ -17,7 +17,6 @@ = time_ago_in_words(note.created_at) ago - else - .alert-message.block-message - %span All files attached to project wall, issues etc will be displayed here + %p.slead All files attached to project wall, issues etc will be displayed here diff --git a/app/views/projects/graph.html.haml b/app/views/projects/graph.html.haml index 07f038d28a236bc2fb6795218a732f59c15944ea..4e0b0e36c343afc6caf338217ed8c3c350e5268d 100644 --- a/app/views/projects/graph.html.haml +++ b/app/views/projects/graph.html.haml @@ -2,13 +2,15 @@ %br .graph_holder %h4 - %small You can move around the graph by using arrow keys. + %small You can move around the graph by using the arrow keys. #holder.graph + .loading.loading-gray + :javascript - var chunk1={commits:#{@commits_json}}; - var days=#{@days_json}; - initGraph(); + var branch_graph; $(function(){ - branchGraph($("#holder")[0]); - GraphNav.init(); + branch_graph = new BranchGraph($("#holder"), { + url: '#{url_for controller: 'projects', action: 'graph', format: :json}', + commit_url: '#{url_for controller: 'projects', action: 'show'}/commits/%s' + }); }); diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 8aaa0e491dd856f348fe14f3f418815b433802a4..f44ed5291827c15df518a9e02281acea5ce09cea 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,6 +1,6 @@ - if @project.valid? :plain - location.href = "#{edit_project_path(@project, notice: 'Project was successfully updated.')}"; + location.href = "#{edit_project_path(@project)}"; - else :plain $('.project_edit_holder').show(); diff --git a/app/views/projects/update_failed.js.haml b/app/views/projects/update_failed.js.haml new file mode 100644 index 0000000000000000000000000000000000000000..a3ac5f4088f0152262b58ea9b51276a3ce3c829b --- /dev/null +++ b/app/views/projects/update_failed.js.haml @@ -0,0 +1,2 @@ +:plain + $(".save-project-loader").replaceWith(errorMessage('#{escape_javascript(@error.message)}')); diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml index 50a817124dcce6331b5eba140993ea2b026717d7..f408fd16c2c7a57de2cbe4c325ea9cdc5674a6d6 100644 --- a/app/views/protected_branches/index.html.haml +++ b/app/views/protected_branches/index.html.haml @@ -39,10 +39,13 @@ - if branch.name == @project.root_ref %span.label default %td - = link_to project_commit_path(@project, branch.commit.id) do - = truncate branch.commit.id.to_s, length: 10 - = time_ago_in_words(branch.commit.committed_date) - ago + - if branch.commit + = link_to project_commit_path(@project, branch.commit.id) do + = truncate branch.commit.id.to_s, length: 10 + = time_ago_in_words(branch.commit.committed_date) + ago + - else + (branch was removed from repository) %td - if can? current_user, :admin_project, @project = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small" diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 0d5f545850a1c599298a2dc212ef8e99f826dd80..8448193deb9c45f97997677c7f4d0494c861faeb 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -23,7 +23,7 @@ %tr %td = link_to project do - %strong.term= project.name + %strong.term= project.name_with_namespace %small.cgray last activity at = project.last_activity_date.stamp("Aug 25, 2011") diff --git a/app/views/services/_gitlab_ci.html.haml b/app/views/services/_gitlab_ci.html.haml index 3c9820b32b4b48804527e15f108e2a041697b2c7..649c5cc4c3c60212e57f7a927b7097c6a38538a9 100644 --- a/app/views/services/_gitlab_ci.html.haml +++ b/app/views/services/_gitlab_ci.html.haml @@ -1,16 +1,19 @@ %h3.page_title - Services → GitLab CI Integration - + GitLab CI + %small Continuous integration server from GitLab .right - .thumbnail - - if @service.active - = image_tag 'service-gitlab-ci.png', class: 'small' - - else - = image_tag 'service-disabled-gitlab-ci.png', class: 'small' + - if @service.active + %small.cgreen Enabled + - else + %small.cgray Disabled -%hr +.back_link + = link_to project_services_path(@project) do + ← to services + +%hr = form_for(@service, :as => :service, :url => project_service_path(@project, :gitlab_ci), :method => :put) do |f| - if @service.errors.any? .alert-message.block-message.error @@ -25,7 +28,7 @@ = f.check_box :active .control-group - = f.label :active, "Project URL", class: "control-label" + = f.label :project_url, "Project URL", class: "control-label" .controls = f.text_field :project_url, class: "input-xlarge", placeholder: "http://ci.gitlabhq.com/projects/3" diff --git a/app/views/services/index.html.haml b/app/views/services/index.html.haml index 3894fcee36902920e036b8ea6246484f7c038079..2c94f965eec1f968a999d6613f2e7ec9ddc858c7 100644 --- a/app/views/services/index.html.haml +++ b/app/views/services/index.html.haml @@ -1,15 +1,31 @@ = render "projects/project_head" %h3.page_title Services -%hr - -.row - .span6 - .padded - %p.slead Continuous integration server from GitLab - .thumbnail.left - = link_to edit_project_service_path(@project, :gitlab_ci) do - - if @gitlab_ci_service.try :active - = image_tag 'service-gitlab-ci.png' - - else - = image_tag 'service-disabled-gitlab-ci.png' +%br +%ul.ui-box.well-list + %li + %h4.cgreen + = link_to edit_project_service_path(@project, :gitlab_ci) do + GitLab CI + %small Continuous integration server from GitLab + .right + - if @gitlab_ci_service.try(:active) + %small.cgreen + %i.icon-ok + Enabled + - else + %small.cgray + %i.icon-off + Disabled + %li.disabled + %h4 + Jenkins CI + %small An extendable open source continuous integration server + .right + %small Not implemented yet + %li.disabled + %h4 + Campfire + %small Web-based group chat tool + .right + %small Not implemented yet diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 924eb3fcae4456ff4fd25da27993a72971943fe3..e283d9b30851a97997c95e6c6a850cff55931424 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,4 +1,4 @@ .input-prepend.project_clone_holder %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH - %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.web_protocol.upcase - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5" + %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge" diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index c75a1d93b70a49ba0c282bdac75515595d1c7422..5fdcea850b27ee32a33da0ec69b39b987b157f2a 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,3 +1,3 @@ - if current_user.require_ssh_key? - %p.error_message - You won't be able to pull or push project code until you #{link_to 'add an SSH key', new_key_path} to your profile + %p.error_message.centered + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_key_path} to your profile diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..ed518300ac05615a7464406189c090c843df3101 --- /dev/null +++ b/app/views/snippets/_blob.html.haml @@ -0,0 +1,12 @@ +.file_holder + .file_title + %i.icon-file + %strong= @snippet.file_name + %span.options + = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" + .file_content.code + - unless @snippet.content.empty? + %div{class: user_color_scheme_class} + = raw @snippet.colorize(formatter: :gitlab) + - else + %p.nothing_here_message Empty file diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index e61e61a7e5e209a7d724caeaa27034848ab7145f..baef737b56524c8a42da9fa64184f23106f7c1d2 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -1,7 +1,8 @@ -%h3= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" +%h3.page_title + = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" %hr -= form_for [@project, @snippet] do |f| - %table.no-borders +.snippet-form-holder + = form_for [@project, @snippet] do |f| -if @snippet.errors.any? .alert-message.block-message.error %ul @@ -10,19 +11,31 @@ .clearfix = f.label :title - .input= f.text_field :title, placeholder: "Example Snippet" - .clearfix - = f.label :file_name - .input= f.text_field :file_name, placeholder: "example.rb" + .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true .clearfix = f.label "Lifetime" .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} .clearfix - = f.label :content, "Code" - .input= f.text_area :content, class: "span8" + .file-editor + = f.label :file_name, "File" + .input + .file_holder.snippet + .file_title + = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true + .file_content.code + %pre#editor= @snippet.content + = f.hidden_field :content, class: 'snippet-file-content' + + .form-actions + = f.submit 'Save', class: "save-btn btn" + = link_to "Cancel", project_snippets_path(@project), class: " btn" + - unless @snippet.new_record? + .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" + + +:javascript + var editor = ace.edit("editor"); + $(".snippet-form-holder form").submit(function(){ + $(".snippet-file-content").val(editor.getValue()); + }); - .actions - = f.submit 'Save', class: "primary btn" - = link_to "Cancel", project_snippets_path(@project), class: " btn" - - unless @snippet.new_record? - .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index a2d3a65e6cb572c4788c76885bba1c7e40c4091c..a576500c15dd2f6676862d6432d2da0d50a0f71d 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -1,12 +1,13 @@ %tr %td + = image_tag gravatar_icon(snippet.author_email), class: "avatar s24" %a{href: project_snippet_path(snippet.project, snippet)} %strong= truncate(snippet.title, length: 60) %td = snippet.file_name %td %span.cgray - - if snippet.expires_at + - if snippet.expires_at = snippet.expires_at.to_date.to_s(:short) - else Never diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 515daec6207b4380a58c95596758d68ed278c736..7b8f94de7dd5d3a25b79e8bb53444b51722ab8c4 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -1,21 +1,21 @@ = render "projects/project_head" -- if can? current_user, :write_snippet, @project - .alert-message.block-message +%h3.page_title + Snippets + %small share code pastes with others out of git repository + + - if can? current_user, :write_snippet, @project = link_to new_project_snippet_path(@project), class: "btn small add_new right", title: "New Snippet" do Add new snippet - Share code pastes with others if it can't be in a git repository - %br - To add new snippet - click on button. - +%br %table %thead %tr %th Title %th File Name %th Expires At - = render @snippets.fresh - - if @snippets.fresh.empty? + = render @snippets + - if @snippets.empty? %tr %td{colspan: 3} %h3.nothing_here_message Nothing here. diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 1b8701e91ed85daa2229e857ee2328959728311c..ad399533a3d23183924fd3a05ef7a95a9f2083c2 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,23 +1,11 @@ = render "projects/project_head" -%h3 +%h3.page_title = @snippet.title %small= @snippet.file_name - if can?(current_user, :admin_snippet, @project) || @snippet.author == current_user = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn small right" %br -%div - .file_holder - .file_title - %i.icon-file - %strong= @snippet.file_name - %span.options - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" - .file_content.code - %div{class: current_user.dark_scheme ? "black" : ""} - = raw @snippet.colorize(options: { linenos: 'True'}) - - -%div - = render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" +%div= render 'blob' +%div#notes= render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml index 92167138081df5b29c5568a2af86c2e312e64c18..e5d9a4a4b5e95ba8037bdab0ccf2ecb3446bd550 100644 --- a/app/views/team_members/_form.html.haml +++ b/app/views/team_members/_form.html.haml @@ -11,7 +11,7 @@ %h6 1. Choose people you want in the team .clearfix = f.label :user_ids, "People" - .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) + .input= select_tag(:user_ids, options_from_collection_for_select(User.active.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) %h6 2. Set access level for them .clearfix diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml index f68f8eb471fb25bc3645cda0403ae859dcf4ce93..8082f47fca8b14f1dada1eb81d606f36f3993e97 100644 --- a/app/views/team_members/_show.html.haml +++ b/app/views/team_members/_show.html.haml @@ -1,26 +1,28 @@ - user = member.user - allow_admin = can? current_user, :admin_project, @project -%tr{id: dom_id(member), class: "team_member_row user_#{user.id}"} - %td.span6 - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 40), class: "avatar s32" - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.email +%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} + .row + .span6 + = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do + = image_tag gravatar_icon(user.email, 40), class: "avatar s32" + = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.email - %td.span5 - .right - - if current_user == user - %span.btn.disabled This is you! - - if @project.owner == user - %span.btn.disabled.success Owner - - elsif user.blocked - %span.btn.disabled.blocked Blocked - - elsif allow_admin - = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do - %i.icon-minus.icon-white + .span5.right + - if allow_admin + .left + = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" + .right + - if current_user == user + %span.btn.disabled This is you! + - if @project.namespace_owner == user + %span.btn.disabled.success Owner + - elsif user.blocked + %span.btn.disabled.blocked Blocked + - elsif allow_admin + = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do + %i.icon-minus.icon-white - - if allow_admin - = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml index 26d13533b5c971093c9126852e4c5224508d9046..462e75af183f93a58a2af8ded15344a7f86b95a0 100644 --- a/app/views/team_members/_team.html.haml +++ b/app/views/team_members/_team.html.haml @@ -1,12 +1,10 @@ - grouper_project_members(@project).each do |access, members| - %table.low - %thead - %tr - %th.span7 - = Project.access_options.key(access).pluralize - %th - %tbody - - members.each do |up| + .ui-box + %h5 + = Project.access_options.key(access).pluralize + %small= members.size + %ul.well-list + - members.sort_by(&:user_name).each do |up| = render(partial: 'team_members/show', locals: {member: up}) diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml index ca3edcf7ffba80b1aa351864bf2d7f12cc21ddf7..e413c81bb6c2fe2ee25bf8ba94c520319c5914d2 100644 --- a/app/views/team_members/index.html.haml +++ b/app/views/team_members/index.html.haml @@ -1,18 +1,20 @@ = render "projects/project_head" %h3.page_title Team Members - %small (#{@project.users_projects.count}) - -- if can? current_user, :admin_team_member, @project - %p.slead + (#{@project.users_projects.count}) + %small Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" + - if can? current_user, :admin_team_member, @project %span.right = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do Import team from another project = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do New Team Member +%hr + - .clearfix -= render partial: "team_members/team", locals: {project: @project} +.clearfix +%div.team-table + = render partial: "team_members/team", locals: {project: @project} diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml index 9d03cd2cb1fc9752172b693037e04da37460b8b0..af9a6e6b92d3af427058372bac9b3e39e2b9fac1 100644 --- a/app/views/team_members/show.html.haml +++ b/app/views/team_members/show.html.haml @@ -6,7 +6,7 @@ = link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger" .profile_avatar_holder = image_tag gravatar_icon(user.email, 60), class: "borders" - %h3 + %h3.page_title = user.name %small = user.email diff --git a/app/views/tree/_head.html.haml b/app/views/tree/_head.html.haml index f8e5c99f06a1c00220aa52f1851a857a62bf70a3..f14526cf23a4dbb440c8cb2aa2dd32ea90952b5d 100644 --- a/app/views/tree/_head.html.haml +++ b/app/views/tree/_head.html.haml @@ -4,4 +4,4 @@ = nav_link(controller: :tree) do = link_to 'Source', project_tree_path(@project, @ref) %li.right - = render "shared/clone_panel" \ No newline at end of file + = render "shared/clone_panel" diff --git a/app/views/tree/_tree.html.haml b/app/views/tree/_tree.html.haml index 02ae3d90c40b131dfe25d97c223949dca2475a92..a632bb3b0d7bb83d02d24086e2434292e0a119af 100644 --- a/app/views/tree/_tree.html.haml +++ b/app/views/tree/_tree.html.haml @@ -16,10 +16,11 @@ - else %table#tree-slider{class: "table_#{@hex_path} tree-table" } %thead - %th Name - %th Last Update - %th Last Commit - %th= link_to "history", project_commits_path(@project, @id), class: "btn very_small right" + %tr + %th Name + %th Last Update + %th Last Commit + %th= link_to "history", project_commits_path(@project, @id), class: "btn very_small right" - if tree.up_dir? %tr.tree-item diff --git a/app/views/tree/blob/_text.html.haml b/app/views/tree/blob/_text.html.haml index 9e0f4bc4bc1af3330e7c143ac4e8e5c42c7b79fb..122e275219d2f10a1c41ec6ff65851d6bf3e2d0e 100644 --- a/app/views/tree/blob/_text.html.haml +++ b/app/views/tree/blob/_text.html.haml @@ -8,8 +8,7 @@ - else .file_content.code - unless blob.empty? - %div{class: current_user.dark_scheme ? "black" : "white"} - = preserve do - = raw blob.colorize(formatter: :gitlab) + %div{class: user_color_scheme_class} + = raw blob.colorize(formatter: :gitlab) - else - %h4.nothing_here_message Empty file + %p.nothing_here_message Empty file diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml index 579ea1b3ad68719a54e336e3365ca3b334567796..c30745390549d6650ed9d4cefab7748929dd4897 100644 --- a/app/views/wikis/show.html.haml +++ b/app/views/wikis/show.html.haml @@ -19,6 +19,3 @@ - if can? current_user, :admin_wiki, @project = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete do Delete this page - -%hr -.wiki_notes#notes= render "notes/notes_with_form", tid: @wiki.id, tt: "wiki" diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 10128660cd4fedbbc8a07422284598d1ac7f0c3e..1414ed490c92d0a8d90317534052860a1aa80f83 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -1,14 +1,18 @@ class PostReceive @queue = :post_receive - def self.perform(reponame, oldrev, newrev, ref, identifier) - project = Project.find_by_path(reponame) + def self.perform(repo_path, oldrev, newrev, ref, identifier) + repo_path.gsub!(Gitlab.config.gitolite.repos_path.to_s, "") + repo_path.gsub!(/.git$/, "") + repo_path.gsub!(/^\//, "") + + project = Project.find_with_namespace(repo_path) return false if project.nil? # Ignore push from non-gitlab users - user = if identifier.eql? Gitlab.config.gitolite_admin_key - email = project.commit(newrev).author.email - User.find_by_email(email) + user = if identifier.eql? Gitlab.config.gitolite.admin_key + email = project.commit(newrev).author.email rescue nil + User.find_by_email(email) if email elsif /^[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}$/.match(identifier) User.find_by_email(identifier) else diff --git a/config/database.yml.example b/config/database.yml.example deleted file mode 100644 index c5a2b8d605bff2bc54fc03858224f2dd395eecc4..0000000000000000000000000000000000000000 --- a/config/database.yml.example +++ /dev/null @@ -1,39 +0,0 @@ -# -# PRODUCTION -# -production: - adapter: mysql2 - encoding: utf8 - reconnect: false - database: gitlabhq_production - pool: 5 - username: root - password: "secure password" - # host: localhost - # socket: /tmp/mysql.sock - -# -# Development specific -# -development: - adapter: mysql2 - encoding: utf8 - reconnect: false - database: gitlabhq_development - pool: 5 - username: root - password: "secure password" - # socket: /tmp/mysql.sock - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: &test - adapter: mysql2 - encoding: utf8 - reconnect: false - database: gitlabhq_test - pool: 5 - username: root - password: "secure password" - # socket: /tmp/mysql.sock diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql index 17b38f3d385d7f2b4b83562cff73f8471d2f28ac..0e873d2b8fb45ca5713caa5680f160d2dfd8d166 100644 --- a/config/database.yml.postgresql +++ b/config/database.yml.postgresql @@ -9,6 +9,7 @@ production: username: postgres password: # host: localhost + # port: 5432 # socket: /tmp/postgresql.sock # diff --git a/config/database.yml.sqlite b/config/database.yml.sqlite deleted file mode 100644 index 591448f6bee5ed2e0f8dfc3e2018ea7a31d8a606..0000000000000000000000000000000000000000 --- a/config/database.yml.sqlite +++ /dev/null @@ -1,31 +0,0 @@ -# -# PRODUCTION -# -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 - -# -# Development specific -# -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: &test - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 3568348971b18276970ffece4db09215450cdf1e..f47625eb132ab304a71e35bf2ccf998fbe413c1f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -1,55 +1,71 @@ -# # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # Gitlab application config file # # # # # # # # # # # # # # # # # # # +# +# How to use: +# 1. copy file as gitlab.yml +# 2. Replace gitlab -> host with your domain +# 3. Replace gitolite -> ssh_host with your domain +# 4. Replace gitlab -> email_from # -# 1. Common settings +# 1. GitLab app settings # ========================== -# Web application specific settings -web: +## GitLab settings +gitlab: + ## Web server settings host: localhost port: 80 https: false -# Email used for notification -# about new issues, comments -email: - from: notify@localhost + ## Email settings + # Email address used in the "From" field in mails sent by GitLab + email_from: gitlab@localhost -# Application specific settings -# Like default project limit for user etc -app: + ## Project settings default_projects_limit: 10 - # backup_path: "/vol/backups" # default: Rails.root + backups/ - # backup_keep_time: 604800 # default: 0 (forever) (in seconds) - # disable_gravatar: true # default: false - Disable user avatars from Gravatar.com + +## Gravatar +gravatar: + enabled: true # Use user avatar images from Gravatar.com (default: true) + # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm + # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm + # # 2. Auth settings # ========================== -ldap: + +## LDAP settings +ldap: enabled: false host: '_your_ldap_server' base: '_the_base_where_you_search_for_users' port: 636 uid: 'sAMAccountName' - method: 'ssl' # plain + method: 'ssl' # "ssl" or "plain" bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' password: '_the_password_of_the_bind_user' +## Omniauth settings omniauth: # Enable ability for users - # to login via twitter, google .. + # Allow logging in via Twitter, Google, etc. using Omniauth providers enabled: false - # IMPORTANT! - # It allows user to login without having user account + # CAUTION! + # This allows users to login without having a user account first (default: false) + # User accounts will be created automatically when authentication was successful. allow_single_sign_on: false + # Locks down those users until they have been cleared by the admin (default: true) block_auto_created_users: true - # Auth providers + ## Auth providers + # Uncomment the lines and fill in the data of the auth provider you want to use + # If your favorite auth provider is not listed you can user others: + # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers providers: # - { name: 'google_oauth2', app_id: 'YOUR APP ID', # app_secret: 'YOUR APP SECRET', @@ -60,29 +76,36 @@ omniauth: # app_secret: 'YOUR APP SECRET' } + # -# 3. Advanced settings: +# 3. Advanced settings # ========================== -# Git Hosting configuration -git_host: +## Backup settings +backup: + path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) + # keep_time: 604800 # default: 0 (forever) (in seconds) + +## Gitolite settings +gitolite: admin_uri: git@localhost:gitolite-admin - base_path: /home/git/repositories/ + repos_path: /home/git/repositories/ hooks_path: /home/git/.gitolite/hooks/ - gitolite_admin_key: gitlab - git_user: git + admin_key: gitlab upload_pack: true receive_pack: true - # host: localhost + ssh_user: git + ssh_host: localhost + # ssh_port: 22 # config_file: gitolite.conf - # port: 22 -# Git settings -# Use default values unless you understand it +## Git settings +# CAUTION! +# Use the default values unless you really know what you are doing git: - path: /usr/bin/git + bin_path: /usr/bin/git # Max size of git object like commit, in bytes # This value can be increased if you have a very large commits - git_max_size: 5242880 # 5.megabytes + max_size: 5242880 # 5.megabytes # Git timeout to read commit, in seconds - git_timeout: 10 + timeout: 10 diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 32af3d077784a435232bab0f7a7734de0d654ecd..4fe3ced4d8d1f7021a4e8cc8064a1ceaf74d65bc 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -2,23 +2,43 @@ class Settings < Settingslogic source "#{Rails.root}/config/gitlab.yml" class << self + # FIXME: Deprecated: remove for 4.1 def web_protocol + ActiveSupport::Deprecation.warn("Settings.web_protocol is deprecated and will be removed from GitLab 4.1", caller) + gitlab.protocol + rescue Settingslogic::MissingSetting self.web['protocol'] ||= web.https ? "https" : "http" end + # FIXME: Deprecated: remove for 4.1 def web_host + ActiveSupport::Deprecation.warn("Settings.web_host is deprecated and will be removed from GitLab 4.1", caller) + gitlab.host + rescue Settingslogic::MissingSetting self.web['host'] ||= 'localhost' end + # FIXME: Deprecated: remove for 4.1 def email_from + ActiveSupport::Deprecation.warn("Settings.email_from is deprecated and will be removed from GitLab 4.1", caller) + gitlab.email_from + rescue Settingslogic::MissingSetting self.email['from'] ||= ("notify@" + web_host) end + # FIXME: Deprecated: remove for 4.1 def url + ActiveSupport::Deprecation.warn("Settings.url is deprecated and will be removed from GitLab 4.1", caller) + gitlab.url + rescue Settingslogic::MissingSetting self['url'] ||= build_url end + # FIXME: Deprecated: remove for 4.1 def web_port + ActiveSupport::Deprecation.warn("Settings.web_port is deprecated and will be removed from GitLab 4.1", caller) + gitlab.port.to_i + rescue Settingslogic::MissingSetting if web.https web['port'] = 443 else @@ -26,11 +46,17 @@ class Settings < Settingslogic end.to_i end + # FIXME: Deprecated: remove for 4.1 def web_custom_port? + ActiveSupport::Deprecation.warn("Settings.web_custom_port? is deprecated and will be removed from GitLab 4.1", caller) + gitlab_on_non_standard_port? + rescue Settingslogic::MissingSetting ![443, 80].include?(web_port) end + # FIXME: Deprecated: remove for 4.1 def build_url + ActiveSupport::Deprecation.warn("Settings.build_url is deprecated and will be removed from GitLab 4.1", caller) if web_custom_port? custom_port = ":#{web_port}" else @@ -44,19 +70,35 @@ class Settings < Settingslogic ].join('') end + # FIXME: Deprecated: remove for 4.1 def ssh_port + ActiveSupport::Deprecation.warn("Settings.ssh_port is deprecated and will be removed from GitLab 4.1", caller) + gitolite.ssh_port + rescue Settingslogic::MissingSetting git_host['port'] || 22 end + # FIXME: Deprecated: remove for 4.1 def ssh_user + ActiveSupport::Deprecation.warn("Settings.ssh_user is deprecated and will be removed from GitLab 4.1", caller) + gitolite.ssh_user + rescue Settingslogic::MissingSetting git_host['git_user'] || 'git' end + # FIXME: Deprecated: remove for 4.1 def ssh_host + ActiveSupport::Deprecation.warn("Settings.ssh_host is deprecated and will be removed from GitLab 4.1", caller) + gitolite.ssh_host + rescue Settingslogic::MissingSetting git_host['host'] || web_host || 'localhost' end + # FIXME: Deprecated: remove for 4.1 def ssh_path + ActiveSupport::Deprecation.warn("Settings.ssh_path is deprecated and will be removed from GitLab 4.1", caller) + gitolite.ssh_path_prefix + rescue Settingslogic::MissingSetting if ssh_port != 22 "ssh://#{ssh_user}@#{ssh_host}:#{ssh_port}/" else @@ -64,15 +106,27 @@ class Settings < Settingslogic end end + # FIXME: Deprecated: remove for 4.1 def git_base_path + ActiveSupport::Deprecation.warn("Settings.git_base_path is deprecated and will be removed from GitLab 4.1", caller) + gitolite.repos_path + rescue Settingslogic::MissingSetting git_host['base_path'] || '/home/git/repositories/' end + # FIXME: Deprecated: remove for 4.1 def git_hooks_path + ActiveSupport::Deprecation.warn("Settings.git_hooks_path is deprecated and will be removed from GitLab 4.1", caller) + gitolite.hooks_path + rescue Settingslogic::MissingSetting git_host['hooks_path'] || '/home/git/share/gitolite/hooks/' end + # FIXME: Deprecated: remove for 4.1 def git_upload_pack + ActiveSupport::Deprecation.warn("Settings.git_upload_pack is deprecated and will be removed from GitLab 4.1", caller) + gitolite.upload_pack + rescue Settingslogic::MissingSetting if git_host['upload_pack'] != false true else @@ -80,7 +134,11 @@ class Settings < Settingslogic end end + # FIXME: Deprecated: remove for 4.1 def git_receive_pack + ActiveSupport::Deprecation.warn("Settings.git_receive_pack is deprecated and will be removed from GitLab 4.1", caller) + gitolite.receive_pack + rescue Settingslogic::MissingSetting if git_host['receive_pack'] != false true else @@ -88,62 +146,207 @@ class Settings < Settingslogic end end + # FIXME: Deprecated: remove for 4.1 def git_bin_path + ActiveSupport::Deprecation.warn("Settings.git_bin_path is deprecated and will be removed from GitLab 4.1", caller) + git.bin_path + rescue Settingslogic::MissingSetting git['path'] || '/usr/bin/git' end + # FIXME: Deprecated: remove for 4.1 def git_max_size + ActiveSupport::Deprecation.warn("Settings.git_max_size is deprecated and will be removed from GitLab 4.1", caller) + git.max_size + rescue Settingslogic::MissingSetting git['git_max_size'] || 5242880 # 5.megabytes end + # FIXME: Deprecated: remove for 4.1 def git_timeout + ActiveSupport::Deprecation.warn("Settings.git_timeout is deprecated and will be removed from GitLab 4.1", caller) + git.timeout + rescue Settingslogic::MissingSetting git['git_timeout'] || 10 end + # FIXME: Deprecated: remove for 4.1 def gitolite_admin_uri + ActiveSupport::Deprecation.warn("Settings.gitolite_admin_uri is deprecated and will be removed from GitLab 4.1", caller) + gitolite.admin_uri + rescue Settingslogic::MissingSetting git_host['admin_uri'] || 'git@localhost:gitolite-admin' end + # FIXME: Deprecated: remove for 4.1 def gitolite_config_file + ActiveSupport::Deprecation.warn("Settings.gitolite_config_file is deprecated and will be removed from GitLab 4.1", caller) + gitolite.config_file + rescue Settingslogic::MissingSetting git_host['config_file'] || 'gitolite.conf' end + # FIXME: Deprecated: remove for 4.1 def gitolite_admin_key + ActiveSupport::Deprecation.warn("Settings.gitolite_admin_key is deprecated and will be removed from GitLab 4.1", caller) + gitolite.admin_key + rescue Settingslogic::MissingSetting git_host['gitolite_admin_key'] || 'gitlab' end + # FIXME: Deprecated: remove for 4.1 def default_projects_limit + ActiveSupport::Deprecation.warn("Settings.default_projects_limit is deprecated and will be removed from GitLab 4.1", caller) + gitlab.default_projects_limit + rescue Settingslogic::MissingSetting app['default_projects_limit'] || 10 end + # FIXME: Deprecated: remove for 4.1 def backup_path - t = app['backup_path'] || "backups/" - t = /^\//.match(t) ? t : Rails.root .join(t) - t + ActiveSupport::Deprecation.warn("Settings.backup_path is deprecated and will be removed from GitLab 4.1", caller) + backup.path + rescue Settingslogic::MissingSetting + File.expand_path(app['backup_path'] || "backups/", Rails.root) end + # FIXME: Deprecated: remove for 4.1 def backup_keep_time + ActiveSupport::Deprecation.warn("Settings.backup_keep_time is deprecated and will be removed from GitLab 4.1", caller) + backup.keep_time + rescue Settingslogic::MissingSetting app['backup_keep_time'] || 0 end + # FIXME: Deprecated: remove for 4.1 def ldap_enabled? - ldap && ldap['enabled'] + ActiveSupport::Deprecation.warn("Settings.ldap_enabled? is deprecated and will be removed from GitLab 4.1", caller) + ldap.enabled rescue Settingslogic::MissingSetting false end + # FIXME: Deprecated: remove for 4.1 def omniauth_enabled? - omniauth && omniauth['enabled'] + ActiveSupport::Deprecation.warn("Settings.omniauth_enabled? is deprecated and will be removed from GitLab 4.1", caller) + omniauth.enabled rescue Settingslogic::MissingSetting false end + # FIXME: Deprecated: remove for 4.1 def omniauth_providers - (omniauth_enabled? && omniauth['providers']) || [] + ActiveSupport::Deprecation.warn("Settings.omniauth_providers is deprecated and will be removed from GitLab 4.1", caller) + omniauth.providers + rescue Settingslogic::MissingSetting + [] end + # FIXME: Deprecated: remove for 4.1 def disable_gravatar? + ActiveSupport::Deprecation.warn("Settings.disable_gravatar? is deprecated and will be removed from GitLab 4.1", caller) + !gravatar.enabled + rescue Settingslogic::MissingSetting app['disable_gravatar'] || false end + + # FIXME: Deprecated: remove for 4.1 + def gravatar_url + ActiveSupport::Deprecation.warn("Settings.gravatar_url is deprecated and will be removed from GitLab 4.1", caller) + gravatar.plain_url + rescue Settingslogic::MissingSetting + app['gravatar_url'] || 'http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm' + end + + # FIXME: Deprecated: remove for 4.1 + def gravatar_ssl_url + ActiveSupport::Deprecation.warn("Settings.gravatar_ssl_url is deprecated and will be removed from GitLab 4.1", caller) + gravatar.ssl_url + rescue Settingslogic::MissingSetting + app['gravatar_ssl_url'] || 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm' + end + + + + def gitlab_on_non_standard_port? + ![443, 80].include?(gitlab.port.to_i) + end + + private + + def build_gitolite_ssh_path_prefix + if gitolite.ssh_port != 22 + "ssh://#{gitolite.ssh_user}@#{gitolite.ssh_host}:#{gitolite.ssh_port}/" + else + "#{gitolite.ssh_user}@#{gitolite.ssh_host}:" + end + end + + def build_gitlab_url + if gitlab_on_non_standard_port? + custom_port = ":#{gitlab.port}" + else + custom_port = nil + end + [ gitlab.protocol, + "://", + gitlab.host, + custom_port + ].join('') + end end end + + +# Default settings + +# FIXME: Deprecated: remove for 4.1 +# all Settings.web ... +# all Settings.app ... +# all Settings.email ... +# all Settings.git_host ... +Settings['pre_40_config'] ||= Settings['web'].present? + +Settings['ldap'] ||= Settingslogic.new({}) +Settings.ldap['enabled'] ||= false + +Settings['omniauth'] ||= Settingslogic.new({}) +Settings.omniauth['enabled'] ||= false +Settings.omniauth['providers'] ||= [] + +Settings['gitlab'] ||= Settingslogic.new({}) +Settings.gitlab['default_projects_limit'] ||= Settings.pre_40_config ? Settings.default_projects_limit : 10 +Settings.gitlab['host'] ||= Settings.pre_40_config ? Settings.web_host : 'localhost' +Settings.gitlab['https'] ||= Settings.pre_40_config ? Settings.web.https : false +Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 +Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" +Settings.gitlab['email_from'] ||= Settings.pre_40_config ? Settings.email_from : "gitlab@#{Settings.gitlab.host}" +Settings.gitlab['url'] ||= Settings.pre_40_config ? Settings.url : Settings.send(:build_gitlab_url) + +Settings['gravatar'] ||= Settingslogic.new({}) +Settings.gravatar['enabled'] ||= Settings.pre_40_config ? !Settings.disable_gravatar? : true +Settings.gravatar['plain_url'] ||= Settings.pre_40_config ? Settings.gravatar_url : 'http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm' +Settings.gravatar['ssl_url'] ||= Settings.pre_40_config ? Settings.gravatar_ssl_url : 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm' + +Settings['gitolite'] ||= Settingslogic.new({}) +Settings.gitolite['admin_key'] ||= Settings.pre_40_config ? Settings.gitolite_admin_key : 'gitlab' +Settings.gitolite['admin_uri'] ||= Settings.pre_40_config ? Settings.gitolite_admin_uri : 'git@localhost:gitolite-admin' +Settings.gitolite['config_file'] ||= Settings.pre_40_config ? Settings.gitolite_config_file : 'gitolite.conf' +Settings.gitolite['hooks_path'] ||= Settings.pre_40_config ? Settings.git_hooks_path : '/home/git/share/gitolite/hooks/' +Settings.gitolite['receive_pack'] ||= Settings.pre_40_config ? Settings.git_receive_pack : (Settings.gitolite['receive_pack'] != false) +Settings.gitolite['repos_path'] ||= Settings.pre_40_config ? Settings.git_base_path : '/home/git/repositories/' +Settings.gitolite['upload_pack'] ||= Settings.pre_40_config ? Settings.git_upload_pack : (Settings.gitolite['upload_pack'] != false) +Settings.gitolite['ssh_host'] ||= Settings.pre_40_config ? Settings.ssh_host : (Settings.gitlab.host || 'localhost') +Settings.gitolite['ssh_port'] ||= Settings.pre_40_config ? Settings.ssh_port : 22 +Settings.gitolite['ssh_user'] ||= Settings.pre_40_config ? Settings.ssh_user : 'git' +Settings.gitolite['ssh_path_prefix'] ||= Settings.pre_40_config ? Settings.ssh_path : Settings.send(:build_gitolite_ssh_path_prefix) + +Settings['backup'] ||= Settingslogic.new({}) +Settings.backup['keep_time'] ||= Settings.pre_40_config ? Settings.backup_keep_time : 0 +Settings.backup['path'] = Settings.pre_40_config ? Settings.backup_path : File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root) + +Settings['git'] ||= Settingslogic.new({}) +Settings.git['max_size'] ||= Settings.pre_40_config ? Settings.git_max_size : 5242880 # 5.megabytes +Settings.git['bin_path'] ||= Settings.pre_40_config ? Settings.git_bin_path : '/usr/bin/git' +Settings.git['timeout'] ||= Settings.pre_40_config ? Settings.git_timeout : 10 +Settings.git['path'] ||= Settings.git.bin_path # FIXME: Deprecated: remove for 4.1 diff --git a/config/initializers/3_grit_ext.rb b/config/initializers/3_grit_ext.rb index d114ea6cc8bfe9cc5507c937160c095bb6c79fb2..097c301a06ab1240979ed0cca19f0008a4cfbe9d 100644 --- a/config/initializers/3_grit_ext.rb +++ b/config/initializers/3_grit_ext.rb @@ -1,8 +1,8 @@ require 'grit' require 'pygments' -Grit::Git.git_timeout = Gitlab.config.git_timeout -Grit::Git.git_max_size = Gitlab.config.git_max_size +Grit::Git.git_timeout = Gitlab.config.git.timeout +Grit::Git.git_max_size = Gitlab.config.git.max_size Grit::Blob.class_eval do include Linguist::BlobHelper diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 8f3cef5a2ac769578a219ad580dd04ee461aab37..ed3ab71862a61662315fa35b37e3920cc1695d1c 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -4,7 +4,7 @@ Devise.setup do |config| # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class with default "from" parameter. - config.mailer_sender = Gitlab.config.email_from + config.mailer_sender = Gitlab.config.gitlab.email_from # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" @@ -205,20 +205,18 @@ Devise.setup do |config| # manager.default_strategies(:scope => :user).unshift :some_external_strategy # end - gl = Gitlab.config - - if gl.ldap_enabled? + if Gitlab.config.ldap.enabled config.omniauth :ldap, - :host => gl.ldap['host'], - :base => gl.ldap['base'], - :uid => gl.ldap['uid'], - :port => gl.ldap['port'], - :method => gl.ldap['method'], - :bind_dn => gl.ldap['bind_dn'], - :password => gl.ldap['password'] + :host => Gitlab.config.ldap['host'], + :base => Gitlab.config.ldap['base'], + :uid => Gitlab.config.ldap['uid'], + :port => Gitlab.config.ldap['port'], + :method => Gitlab.config.ldap['method'], + :bind_dn => Gitlab.config.ldap['bind_dn'], + :password => Gitlab.config.ldap['password'] end - gl.omniauth_providers.each do |gl_provider| - config.omniauth gl_provider['name'].to_sym, gl_provider['app_id'], gl_provider['app_secret'] + Gitlab.config.omniauth.providers.each do |provider| + config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'] end end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 3549b8362bb4042339a5f4412d56b70bb87b7a9d..8f8bef42befd6c13ced10f6345d7d40cb7bec968 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -4,4 +4,5 @@ # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone -Mime::Type.register_alias 'text/plain', :patch +Mime::Type.register_alias "text/plain", :diff +Mime::Type.register_alias "text/plain", :patch diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index a78cb6b670ba4a096b36259c3f8068556f8b6cb9..3b763cf410de7e0a0965b4e300f91ad0cf1ceaf8 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -14,7 +14,7 @@ en: devise: failure: already_authenticated: 'You are already signed in.' - unauthenticated: 'You need to sign in or sign up before continuing.' + unauthenticated: 'You need to sign in before continuing.' unconfirmed: 'You have to confirm your account before continuing.' locked: 'Your account is locked.' invalid: 'Invalid email or password.' diff --git a/config/routes.rb b/config/routes.rb index 98cf7e812c90b138debe0d8c4a80e8b08d7db6f6..e08bfebc0202fa089cc8cee31125f8615130dfd9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,11 +14,11 @@ Gitlab::Application.routes.draw do # Enable Grack support mount Grack::Bundle.new({ - git_path: Gitlab.config.git_bin_path, - project_root: Gitlab.config.git_base_path, - upload_pack: Gitlab.config.git_upload_pack, - receive_pack: Gitlab.config.git_receive_pack - }), at: '/:path', constraints: { path: /[\w\.-]+\.git/ } + git_path: Gitlab.config.git.bin_path, + project_root: Gitlab.config.gitolite.repos_path, + upload_pack: Gitlab.config.gitolite.upload_pack, + receive_pack: Gitlab.config.gitolite.receive_pack + }), at: '/:path', constraints: { path: /[-\/\w\.-]+\.git/ } # # Help @@ -31,6 +31,7 @@ Gitlab::Application.routes.draw do get 'help/system_hooks' => 'help#system_hooks' get 'help/markdown' => 'help#markdown' get 'help/ssh' => 'help#ssh' + get 'help/raketasks' => 'help#raketasks' # # Admin Area @@ -49,7 +50,7 @@ Gitlab::Application.routes.draw do delete :remove_project end end - resources :projects, constraints: { id: /[^\/]+/ } do + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do member do get :team put :team_update @@ -69,14 +70,18 @@ Gitlab::Application.routes.draw do # # Profile Area # - get "profile/account" => "profile#account" - get "profile/history" => "profile#history" - put "profile/password" => "profile#password_update" - get "profile/token" => "profile#token" - put "profile/reset_private_token" => "profile#reset_private_token" - get "profile" => "profile#show" - get "profile/design" => "profile#design" - put "profile/update" => "profile#update" + resource :profile, only: [:show, :update] do + member do + get :account + get :history + get :token + get :design + + put :update_password + put :reset_private_token + put :update_username + end + end resources :keys @@ -107,7 +112,7 @@ Gitlab::Application.routes.draw do # # Project Area # - resources :projects, constraints: { id: /[^\/]+/ }, except: [:new, :create, :index], path: "/" do + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do member do get "wall" get "graph" @@ -159,12 +164,12 @@ Gitlab::Application.routes.draw do end end - resources :merge_requests do + resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do member do get :diffs get :automerge get :automerge_check - get :raw + get :ci_status end collection do @@ -195,9 +200,9 @@ Gitlab::Application.routes.draw do :via => [:get, :post], constraints: {from: /.+/, to: /.+/} resources :team, controller: 'team_members', only: [:index] - resources :milestones + resources :milestones, except: [:destroy] resources :labels, only: [:index] - resources :issues do + resources :issues, except: [:destroy] do collection do post :sort post :bulk_update diff --git a/db/fixtures/development/001_admin.rb b/db/fixtures/development/001_admin.rb index c857f6bcb3fa6caa416c980a2c6d564238a5fbc6..fbe41e4d22d6e0edbad1a6f83ffcf50c0fd9890b 100644 --- a/db/fixtures/development/001_admin.rb +++ b/db/fixtures/development/001_admin.rb @@ -1,21 +1,11 @@ -unless User.count > 0 - admin = User.create( - :email => "admin@local.host", - :name => "Administrator", - :password => "5iveL!fe", - :password_confirmation => "5iveL!fe" - ) - - admin.projects_limit = 10000 - admin.admin = true - admin.save! - - if admin.valid? - puts %q[ - Administrator account created: - - login.........admin@local.host - password......5iveL!fe - ] - end -end +User.seed(:id, [ + { + id: 1, + name: "Administrator", + email: "admin@local.host", + username: 'root', + password: "5iveL!fe", + password_confirmation: "5iveL!fe", + admin: true, + } +]) diff --git a/db/fixtures/development/002_project.rb b/db/fixtures/development/002_project.rb index eb68b5fe93a956b0789bc3b0e4ebc2f258bfa1de..4db11a878ec775a512a13c135b2ed95cd0f680bf 100644 --- a/db/fixtures/development/002_project.rb +++ b/db/fixtures/development/002_project.rb @@ -1,5 +1,14 @@ +Group.seed(:id, [ + { id: 100, name: "Brightbox", path: 'brightbox', owner_id: 1 }, + { id: 101, name: "KDE", path: 'kde', owner_id: 1 }, +]) + Project.seed(:id, [ - { id: 1, name: "Underscore.js", path: "underscore", code: "underscore", owner_id: 1 }, - { id: 2, name: "Diaspora", path: "diaspora", code: "diaspora", owner_id: 1 }, - { id: 3, name: "Ruby on Rails", path: "rails", code: "rails", owner_id: 1 } + { id: 1, name: "Underscore.js", path: "underscore", owner_id: 1 }, + { id: 2, name: "Diaspora", path: "diaspora", owner_id: 1 }, + { id: 3, namespace_id: 100, name: "Brightbox CLI", path: "brightbox-cli", owner_id: 1 }, + { id: 4, namespace_id: 100, name: "Puppet", path: "puppet", owner_id: 1 }, + { id: 5, namespace_id: 101, name: "kdebase", path: "kdebase", owner_id: 1}, + { id: 6, namespace_id: 101, name: "kdelibs", path: "kdelibs", owner_id: 1}, + { id: 7, namespace_id: 101, name: "amarok", path: "amarok", owner_id: 1}, ]) diff --git a/db/fixtures/development/003_users.rb b/db/fixtures/development/003_users.rb index 309eb90b1bfcc1aa538d2cb739af4bfb92e2832e..25705f1b72666439e2f153c8c9c22301855a7c1a 100644 --- a/db/fixtures/development/003_users.rb +++ b/db/fixtures/development/003_users.rb @@ -1,11 +1,11 @@ User.seed(:id, [ - { :id => 2, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 3, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 4, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 5, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 6, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 7, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 8, :name => Faker::Internet.user_name, :email => Faker::Internet.email}, - { :id => 9, :name => Faker::Internet.user_name, :email => Faker::Internet.email} + { id: 2, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 3, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 4, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 5, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 6, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 7, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 8, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email}, + { id: 9, username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email} ]) diff --git a/db/fixtures/development/009_source_code.rb b/db/fixtures/development/009_source_code.rb index 489bd02ea3247de80bd4e642a85cd2326c9fd79e..6b9b6584a58b215dccd06c4fef548373ed1e22fd 100644 --- a/db/fixtures/development/009_source_code.rb +++ b/db/fixtures/development/009_source_code.rb @@ -3,7 +3,8 @@ root = Gitlab.config.git_base_path projects = [ { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' }, { path: 'diaspora.git', git: 'https://github.com/diaspora/diaspora.git' }, - { path: 'rails.git', git: 'https://github.com/rails/rails.git' }, + { path: 'brightbox/brightbox-cli.git', git: 'https://github.com/brightbox/brightbox-cli.git' }, + { path: 'brightbox/puppet.git', git: 'https://github.com/brightbox/puppet.git' }, ] projects.each do |project| @@ -13,9 +14,10 @@ projects.each do |project| next if File.exists?(project_path) cmds = [ - "cd #{root} && sudo -u git -H git clone --bare #{project[:git]}", - "sudo cp ./lib/hooks/post-receive #{project_path}/hooks/post-receive", - "sudo chown git:git #{project_path}/hooks/post-receive" + "cd #{root} && sudo -u git -H git clone --bare #{project[:git]} ./#{project[:path]}", + "sudo ln -s ./lib/hooks/post-receive #{project_path}/hooks/post-receive", + "sudo chown git:git -R #{project_path}", + "sudo chmod 770 -R #{project_path}", ] cmds.each do |cmd| diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index cfff6bf8bc29f57a570a68d6687903670b464c50..f119694d11d1025b17ee742e1024860efdc5b13f 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,8 +1,9 @@ admin = User.create( - :email => "admin@local.host", - :name => "Administrator", - :password => "5iveL!fe", - :password_confirmation => "5iveL!fe" + email: "admin@local.host", + name: "Administrator", + username: 'root', + password: "5iveL!fe", + password_confirmation: "5iveL!fe" ) admin.projects_limit = 10000 diff --git a/db/migrate/20121122145155_convert_group_to_namespace.rb b/db/migrate/20121122145155_convert_group_to_namespace.rb new file mode 100644 index 0000000000000000000000000000000000000000..fc8b023d814f3694329d36d06190d94706be77d5 --- /dev/null +++ b/db/migrate/20121122145155_convert_group_to_namespace.rb @@ -0,0 +1,13 @@ +class ConvertGroupToNamespace < ActiveRecord::Migration + def up + rename_table 'groups', 'namespaces' + add_column :namespaces, :type, :string, null: true + + # Migrate old groups + Namespace.update_all(type: 'Group') + end + + def down + raise 'Rollback is not allowed' + end +end diff --git a/db/migrate/20121122150932_add_namespace_id_to_project.rb b/db/migrate/20121122150932_add_namespace_id_to_project.rb new file mode 100644 index 0000000000000000000000000000000000000000..904f3aa32be7ba5a6edfa0201ba5cf12cd13f9f0 --- /dev/null +++ b/db/migrate/20121122150932_add_namespace_id_to_project.rb @@ -0,0 +1,5 @@ +class AddNamespaceIdToProject < ActiveRecord::Migration + def change + rename_column :projects, :group_id, :namespace_id + end +end diff --git a/db/migrate/20121123104937_add_username_to_user.rb b/db/migrate/20121123104937_add_username_to_user.rb new file mode 100644 index 0000000000000000000000000000000000000000..04232a119d92b6e651753df4f06aefd6aa51018f --- /dev/null +++ b/db/migrate/20121123104937_add_username_to_user.rb @@ -0,0 +1,5 @@ +class AddUsernameToUser < ActiveRecord::Migration + def change + add_column :users, :username, :string, null: true + end +end diff --git a/db/migrate/20121123164910_rename_code_to_path.rb b/db/migrate/20121123164910_rename_code_to_path.rb new file mode 100644 index 0000000000000000000000000000000000000000..fb10baf58cf4660fdbd18a81d9882ff142892a1b --- /dev/null +++ b/db/migrate/20121123164910_rename_code_to_path.rb @@ -0,0 +1,11 @@ +class RenameCodeToPath < ActiveRecord::Migration + def up + remove_column :projects, :code + rename_column :namespaces, :code, :path + end + + def down + add_column :projects, :code, :string + rename_column :namespaces, :path, :code + end +end diff --git a/db/migrate/20121203154450_add_events_indices.rb b/db/migrate/20121203154450_add_events_indices.rb new file mode 100644 index 0000000000000000000000000000000000000000..502a2ccbcdab0bba3d23b38873d18ae7071e9be9 --- /dev/null +++ b/db/migrate/20121203154450_add_events_indices.rb @@ -0,0 +1,8 @@ +class AddEventsIndices < ActiveRecord::Migration + def change + add_index :events, :project_id + add_index :events, :author_id + add_index :events, :action + add_index :events, :target_type + end +end diff --git a/db/migrate/20121203160507_more_indices.rb b/db/migrate/20121203160507_more_indices.rb new file mode 100644 index 0000000000000000000000000000000000000000..52170a7c597dd8bc7c87e52d09ab5cd0909b9df1 --- /dev/null +++ b/db/migrate/20121203160507_more_indices.rb @@ -0,0 +1,26 @@ +class MoreIndices < ActiveRecord::Migration + def change + add_index :notes, :project_id + add_index :namespaces, :owner_id + add_index :keys, :user_id + + add_index :projects, :namespace_id + add_index :projects, :owner_id + + add_index :services, :project_id + add_index :snippets, :project_id + + add_index :users_projects, :project_id + + # Issues + add_index :issues, :assignee_id + add_index :issues, :milestone_id + add_index :issues, :author_id + + # Merge Requests + add_index :merge_requests, :assignee_id + add_index :merge_requests, :milestone_id + add_index :merge_requests, :author_id + + end +end diff --git a/db/migrate/20121205201726_add_more_indexes.rb b/db/migrate/20121205201726_add_more_indexes.rb new file mode 100644 index 0000000000000000000000000000000000000000..a2b36f7fdf9a4bac17ebf5b64e1181f577a58cc0 --- /dev/null +++ b/db/migrate/20121205201726_add_more_indexes.rb @@ -0,0 +1,44 @@ +class AddMoreIndexes < ActiveRecord::Migration + def change + add_index :events, :created_at + add_index :events, :target_id + + add_index :issues, :closed + add_index :issues, :created_at + add_index :issues, :title + + add_index :keys, :identifier + # FIXME: MySQL can't index text columns + #add_index :keys, :key + add_index :keys, :project_id + + add_index :merge_requests, :closed + add_index :merge_requests, :created_at + add_index :merge_requests, :source_branch + add_index :merge_requests, :target_branch + add_index :merge_requests, :title + + add_index :milestones, :due_date + add_index :milestones, :project_id + + add_index :namespaces, :name + add_index :namespaces, :path + add_index :namespaces, :type + + add_index :notes, :created_at + + add_index :snippets, :created_at + add_index :snippets, :expires_at + + add_index :users, :admin + add_index :users, :blocked + add_index :users, :name + add_index :users, :username + + add_index :users_projects, :project_access + add_index :users_projects, :user_id + + add_index :wikis, :project_id + add_index :wikis, :slug + end +end diff --git a/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb b/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb new file mode 100644 index 0000000000000000000000000000000000000000..6f2da4136a3cad9623e68ce7c6021abbe3358ad2 --- /dev/null +++ b/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb @@ -0,0 +1,20 @@ +class MoveNoteableCommitToOwnField < ActiveRecord::Migration + def up + add_column :notes, :commit_id, :string, null: true + add_column :notes, :new_noteable_id, :integer, null: true + Note.where(noteable_type: 'Commit').update_all('commit_id = noteable_id') + + if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' + Note.where("noteable_type != 'Commit'").update_all('new_noteable_id = CAST (noteable_id AS INTEGER)') + else + Note.where("noteable_type != 'Commit'").update_all('new_noteable_id = noteable_id') + end + + remove_column :notes, :noteable_id + rename_column :notes, :new_noteable_id, :noteable_id + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20121219095402_indices_for_notes.rb b/db/migrate/20121219095402_indices_for_notes.rb new file mode 100644 index 0000000000000000000000000000000000000000..4c5d041ce815eada2db44b572a5eb75973ce58a7 --- /dev/null +++ b/db/migrate/20121219095402_indices_for_notes.rb @@ -0,0 +1,6 @@ +class IndicesForNotes < ActiveRecord::Migration + def change + add_index :notes, :commit_id + add_index :notes, [:project_id, :noteable_type] + end +end diff --git a/db/schema.rb b/db/schema.rb index 27b1f4aa84af8df04c43f06d28861ec97ea4440f..7de5593285a535bab81a95c52641c4927fa586cd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20121120113838) do +ActiveRecord::Schema.define(:version => 20121219095402) do create_table "events", :force => true do |t| t.string "target_type" @@ -25,13 +25,12 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "author_id" end - create_table "groups", :force => true do |t| - t.string "name", :null => false - t.string "code", :null => false - t.integer "owner_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end + add_index "events", ["action"], :name => "index_events_on_action" + add_index "events", ["author_id"], :name => "index_events_on_author_id" + add_index "events", ["created_at"], :name => "index_events_on_created_at" + add_index "events", ["project_id"], :name => "index_events_on_project_id" + add_index "events", ["target_id"], :name => "index_events_on_target_id" + add_index "events", ["target_type"], :name => "index_events_on_target_type" create_table "issues", :force => true do |t| t.string "title" @@ -47,7 +46,13 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "milestone_id" end + add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id" + add_index "issues", ["author_id"], :name => "index_issues_on_author_id" + add_index "issues", ["closed"], :name => "index_issues_on_closed" + add_index "issues", ["created_at"], :name => "index_issues_on_created_at" + add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id" add_index "issues", ["project_id"], :name => "index_issues_on_project_id" + add_index "issues", ["title"], :name => "index_issues_on_title" create_table "keys", :force => true do |t| t.integer "user_id" @@ -59,6 +64,10 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "project_id" end + add_index "keys", ["identifier"], :name => "index_keys_on_identifier" + add_index "keys", ["project_id"], :name => "index_keys_on_project_id" + add_index "keys", ["user_id"], :name => "index_keys_on_user_id" + create_table "merge_requests", :force => true do |t| t.string "target_branch", :null => false t.string "source_branch", :null => false @@ -76,7 +85,15 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "milestone_id" end + add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" + add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" + add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed" + add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" + add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" + add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch" + add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch" + add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" create_table "milestones", :force => true do |t| t.string "title", :null => false @@ -88,9 +105,25 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "updated_at", :null => false end + add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date" + add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id" + + create_table "namespaces", :force => true do |t| + t.string "name", :null => false + t.string "path", :null => false + t.integer "owner_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "type" + end + + add_index "namespaces", ["name"], :name => "index_namespaces_on_name" + add_index "namespaces", ["owner_id"], :name => "index_namespaces_on_owner_id" + add_index "namespaces", ["path"], :name => "index_namespaces_on_path" + add_index "namespaces", ["type"], :name => "index_namespaces_on_type" + create_table "notes", :force => true do |t| t.text "note" - t.string "noteable_id" t.string "noteable_type" t.integer "author_id" t.datetime "created_at", :null => false @@ -98,10 +131,15 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "project_id" t.string "attachment" t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" end - add_index "notes", ["noteable_id"], :name => "index_notes_on_noteable_id" + add_index "notes", ["commit_id"], :name => "index_notes_on_commit_id" + add_index "notes", ["created_at"], :name => "index_notes_on_created_at" add_index "notes", ["noteable_type"], :name => "index_notes_on_noteable_type" + add_index "notes", ["project_id", "noteable_type"], :name => "index_notes_on_project_id_and_noteable_type" + add_index "notes", ["project_id"], :name => "index_notes_on_project_id" create_table "projects", :force => true do |t| t.string "name" @@ -110,16 +148,18 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.boolean "private_flag", :default => true, :null => false - t.string "code" t.integer "owner_id" t.string "default_branch" t.boolean "issues_enabled", :default => true, :null => false t.boolean "wall_enabled", :default => true, :null => false t.boolean "merge_requests_enabled", :default => true, :null => false t.boolean "wiki_enabled", :default => true, :null => false - t.integer "group_id" + t.integer "namespace_id" end + add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id" + add_index "projects", ["owner_id"], :name => "index_projects_on_owner_id" + create_table "protected_branches", :force => true do |t| t.integer "project_id", :null => false t.string "name", :null => false @@ -138,6 +178,8 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.string "project_url" end + add_index "services", ["project_id"], :name => "index_services_on_project_id" + create_table "snippets", :force => true do |t| t.string "title" t.text "content" @@ -149,6 +191,10 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "expires_at" end + add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at" + add_index "snippets", ["expires_at"], :name => "index_snippets_on_expires_at" + add_index "snippets", ["project_id"], :name => "index_snippets_on_project_id" + create_table "taggings", :force => true do |t| t.integer "tag_id" t.integer "taggable_id" @@ -194,11 +240,16 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.datetime "locked_at" t.string "extern_uid" t.string "provider" + t.string "username" end + add_index "users", ["admin"], :name => "index_users_on_admin" + add_index "users", ["blocked"], :name => "index_users_on_blocked" add_index "users", ["email"], :name => "index_users_on_email", :unique => true add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true + add_index "users", ["name"], :name => "index_users_on_name" add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["username"], :name => "index_users_on_username" create_table "users_projects", :force => true do |t| t.integer "user_id", :null => false @@ -208,6 +259,10 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "project_access", :default => 0, :null => false end + add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access" + add_index "users_projects", ["project_id"], :name => "index_users_projects_on_project_id" + add_index "users_projects", ["user_id"], :name => "index_users_projects_on_user_id" + create_table "web_hooks", :force => true do |t| t.string "url" t.integer "project_id" @@ -227,4 +282,7 @@ ActiveRecord::Schema.define(:version => 20121120113838) do t.integer "user_id" end + add_index "wikis", ["project_id"], :name => "index_wikis_on_project_id" + add_index "wikis", ["slug"], :name => "index_wikis_on_slug" + end diff --git a/doc/api/README.md b/doc/api/README.md index 19b7ff20bf33de5fe89aa5e67690c6c799d70f78..477429c9fa09a64c8e1d9ef8cd3eddd9c4e139ad 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -15,7 +15,7 @@ API requests should be prefixed with `api` and the API version. The API version Example of a valid API request: ``` -GET http://example.com/api/v2/projects?private_token=QVy1PB7sTxfy4pqfZM1U +GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U ``` The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL. @@ -25,7 +25,7 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en When listing resources you can pass the following parameters: + `page` (default: `1`) - page number -+ `per_page` (default: `20`, max: `100`) - how many items to list per page ++ `per_page` (default: `20`, max: `100`) - number of items to list per page ## Contents @@ -36,3 +36,4 @@ When listing resources you can pass the following parameters: + [Repositories](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/repositories.md) + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) + [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md) ++ [Notes](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/notes.md) diff --git a/doc/api/issues.md b/doc/api/issues.md index aaad33054896f8a10b1a714dfc86c65c13159a1a..0383b6760736326ee29fb1aaa06bcd9765be9b23 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -18,6 +18,7 @@ GET /issues "assignee": null, "author": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -46,6 +47,7 @@ GET /issues }, "assignee": { "id": 2, + "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", "blocked": false, @@ -53,6 +55,7 @@ GET /issues }, "author": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -75,7 +78,7 @@ GET /projects/:id/issues Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ## Single issue @@ -87,7 +90,7 @@ GET /projects/:id/issues/:issue_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `issue_id` (required) - The ID of a project issue ```json @@ -110,6 +113,7 @@ Parameters: }, "assignee": { "id": 2, + "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", "blocked": false, @@ -117,6 +121,7 @@ Parameters: }, "author": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -138,7 +143,7 @@ POST /projects/:id/issues Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `title` (required) - The title of an issue + `description` (optional) - The description of an issue + `assignee_id` (optional) - The ID of a user to assign issue @@ -157,7 +162,7 @@ PUT /projects/:id/issues/:issue_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `issue_id` (required) - The ID of a project's issue + `title` (optional) - The title of an issue + `description` (optional) - The description of an issue @@ -168,17 +173,3 @@ Parameters: Will return updated issue with status `200 OK` on success, or `404 Not found` on fail. -## Delete issue - -Delete existing project issue. - -``` -DELETE /projects/:id/issues/:issue_id -``` - -Parameters: - -+ `id` (required) - The ID or code name of a project -+ `issue_id` (required) - The ID of a project's issue - -Status code `200` will be returned on success. diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index e5b067a61d949715ffd7f7e14f54bb9588fb0b87..525c55d12c2581167015c7212a4ff6260bfcbffc 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -8,7 +8,7 @@ GET /projects/:id/merge_requests Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ```json [ @@ -22,6 +22,7 @@ Parameters: "merged":false, "author":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -29,6 +30,7 @@ Parameters: }, "assignee":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -48,7 +50,7 @@ GET /projects/:id/merge_request/:merge_request_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `merge_request_id` (required) - The ID of MR ```json @@ -62,6 +64,7 @@ Parameters: "merged":false, "author":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -69,6 +72,7 @@ Parameters: }, "assignee":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -88,7 +92,7 @@ POST /projects/:id/merge_requests Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `source_branch` (required) - The source branch + `target_branch` (required) - The target branch + `assignee_id` - Assignee user ID @@ -105,6 +109,7 @@ Parameters: "merged":false, "author":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -112,6 +117,7 @@ Parameters: }, "assignee":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -130,7 +136,7 @@ PUT /projects/:id/merge_request/:merge_request_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `merge_request_id` (required) - ID of MR + `source_branch` - The source branch + `target_branch` - The target branch @@ -150,6 +156,7 @@ Parameters: "merged":false, "author":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -157,6 +164,7 @@ Parameters: }, "assignee":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, @@ -174,7 +182,7 @@ POST /projects/:id/merge_request/:merge_request_id/comments Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `merge_request_id` (required) - ID of MR + `note` (required) - Text of comment @@ -184,6 +192,7 @@ Will return created note with status `201 Created` on success, or `404 Not found { "author":{ "id":1, + "username": "admin", "email":"admin@local.host", "name":"Administrator", "blocked":false, diff --git a/doc/api/milestones.md b/doc/api/milestones.md index f68d8eb7d58ebb6802dfd189b071d709d0aa6b69..b997e83901bb506e4fbc439c43eb94987039c3f8 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -8,7 +8,7 @@ GET /projects/:id/milestones Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ## Single milestone @@ -20,7 +20,7 @@ GET /projects/:id/milestones/:milestone_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `milestone_id` (required) - The ID of a project milestone ## New milestone @@ -33,7 +33,7 @@ POST /projects/:id/milestones Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `milestone_id` (required) - The ID of a project milestone + `title` (required) - The title of an milestone + `description` (optional) - The description of the milestone @@ -49,7 +49,7 @@ PUT /projects/:id/milestones/:milestone_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `milestone_id` (required) - The ID of a project milestone + `title` (optional) - The title of a milestone + `description` (optional) - The description of a milestone diff --git a/doc/api/notes.md b/doc/api/notes.md new file mode 100644 index 0000000000000000000000000000000000000000..bb33efb8c25dfdbb0f93384a2d4d34f27af5608b --- /dev/null +++ b/doc/api/notes.md @@ -0,0 +1,150 @@ +## List notes + +### List project wall notes + +Get a list of project wall notes. + +``` +GET /projects/:id/notes +``` + +```json +[ + { + "id": 522, + "body": "The solution is rather tricky", + "author": { + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "blocked": false, + "created_at": "2012-05-23T08:00:58Z" + }, + "created_at": "2012-11-27T19:16:44Z" + } +] +``` + +Parameters: + ++ `id` (required) - The ID of a project + +### List issue notes + +Get a list of issue notes. + +``` +GET /projects/:id/issues/:issue_id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `issue_id` (required) - The ID of an issue + +### List snippet notes + +Get a list of snippet notes. + +``` +GET /projects/:id/snippets/:snippet_id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `snippet_id` (required) - The ID of a snippet + +## Single note + +### Single wall note + +Get a wall note. + +``` +GET /projects/:id/notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `note_id` (required) - The ID of a wall note + +### Single issue note + +Get an issue note. + +``` +GET /projects/:id/issues/:issue_id/:notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `issue_id` (required) - The ID of a project issue ++ `note_id` (required) - The ID of an issue note + +### Single snippet note + +Get a snippet note. + +``` +GET /projects/:id/issues/:snippet_id/:notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `snippet_id` (required) - The ID of a project snippet ++ `note_id` (required) - The ID of an snippet note + +## New note + +### New wall note + +Create a new wall note. + +``` +POST /projects/:id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. + + +### New issue note + +Create a new issue note. + +``` +POST /projects/:id/issues/:issue_id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `issue_id` (required) - The ID of an issue ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. + +### New snippet note + +Create a new snippet note. + +``` +POST /projects/:id/snippets/:snippet_id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `snippet_id` (required) - The ID of an snippet ++ `body` (required) - The content of a note + +Will return created note with status `201 Created` on success, or `404 Not found` on fail. diff --git a/doc/api/projects.md b/doc/api/projects.md index fdedf904a3a50a71d9a8dfa3de1ebd6de8916372..411286750f856b9dfa19425bf4bc0f6432d7e7af 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -10,13 +10,12 @@ GET /projects [ { "id": 3, - "code": "rails", "name": "rails", "description": null, - "path": "rails", "default_branch": "master", "owner": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -31,13 +30,12 @@ GET /projects }, { "id": 5, - "code": "gitlab", "name": "gitlab", "description": null, - "path": "gitlab", "default_branch": "api", "owner": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -63,18 +61,17 @@ GET /projects/:id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ```json { "id": 5, - "code": "gitlab", "name": "gitlab", "description": null, - "path": "gitlab", "default_branch": "api", "owner": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -100,8 +97,6 @@ POST /projects Parameters: + `name` (required) - new project name -+ `code` (optional) - new project code, uses project name if not set -+ `path` (optional) - new project path, uses project name if not set + `description` (optional) - short project description + `default_branch` (optional) - 'master' by default + `issues_enabled` (optional) - enabled by default @@ -122,7 +117,8 @@ GET /projects/:id/members Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ++ `query` - Query string ## Get project team member @@ -134,13 +130,14 @@ GET /projects/:id/members/:user_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `user_id` (required) - The ID of a user ```json { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -159,7 +156,7 @@ POST /projects/:id/members Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `user_id` (required) - The ID of a user to add + `access_level` (required) - Project access level @@ -175,7 +172,7 @@ PUT /projects/:id/members/:user_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `user_id` (required) - The ID of a team member + `access_level` (required) - Project access level @@ -191,7 +188,7 @@ DELETE /projects/:id/members/:user_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `user_id` (required) - The ID of a team member Status code `200` will be returned on success. @@ -206,7 +203,7 @@ GET /projects/:id/hooks Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project Will return hooks with status `200 OK` on success, or `404 Not found` on fail. @@ -220,7 +217,7 @@ GET /projects/:id/hooks/:hook_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `hook_id` (required) - The ID of a project hook Will return hook with status `200 OK` on success, or `404 Not found` on fail. @@ -235,7 +232,7 @@ POST /projects/:id/hooks Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `url` (required) - The hook URL Will return status `201 Created` on success, or `404 Not found` on fail. @@ -250,7 +247,7 @@ PUT /projects/:id/hooks/:hook_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `hook_id` (required) - The ID of a project hook + `url` (required) - The hook URL @@ -267,7 +264,7 @@ DELETE /projects/:id/hooks Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `hook_id` (required) - The ID of hook to delete Will return status `200 OK` on success, or `404 Not found` on fail. diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 487ad9b27184b5dcba1a9e12adbf9c8c40318515..685797ad78d7a71098f2a6c75b611c71f945c5ce 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -8,7 +8,7 @@ GET /projects/:id/repository/branches Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ```json [ @@ -48,7 +48,7 @@ GET /projects/:id/repository/branches/:branch Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `branch` (required) - The name of the branch ```json @@ -87,7 +87,7 @@ GET /projects/:id/repository/tags Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ```json [ @@ -125,7 +125,7 @@ GET /projects/:id/repository/commits Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `ref_name` (optional) - The name of a repository branch or tag ```json @@ -159,7 +159,7 @@ GET /projects/:id/repository/commits/:sha/blob Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `sha` (required) - The commit or branch name + `filepath` (required) - The path the file diff --git a/doc/api/session.md b/doc/api/session.md index 9fdbeb439a4047c52c84a9b0702e23d7386f65ef..c7e57aaca7a896b62a769ad5c31bea51060e7b4a 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -13,6 +13,7 @@ Parameters: ```json { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "private_token": "dd34asd13as", diff --git a/doc/api/snippets.md b/doc/api/snippets.md index 288fd5296f672638cf324242c8a88ecfc42f81a3..ceb8a63d06fb6591efac268a4a32670ac54a0819 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -8,7 +8,7 @@ GET /projects/:id/snippets Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project ## Single snippet @@ -20,7 +20,7 @@ GET /projects/:id/snippets/:snippet_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `snippet_id` (required) - The ID of a project's snippet ```json @@ -30,6 +30,7 @@ Parameters: "file_name": "add.rb", "author": { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -51,7 +52,7 @@ GET /projects/:id/snippets/:snippet_id/raw Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `snippet_id` (required) - The ID of a project's snippet ## New snippet @@ -64,7 +65,7 @@ POST /projects/:id/snippets Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `title` (required) - The title of a snippet + `file_name` (required) - The name of a snippet file + `lifetime` (optional) - The expiration date of a snippet @@ -82,7 +83,7 @@ PUT /projects/:id/snippets/:snippet_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `snippet_id` (required) - The ID of a project's snippet + `title` (optional) - The title of a snippet + `file_name` (optional) - The name of a snippet file @@ -101,7 +102,7 @@ DELETE /projects/:id/snippets/:snippet_id Parameters: -+ `id` (required) - The ID or code name of a project ++ `id` (required) - The ID of a project + `snippet_id` (required) - The ID of a project's snippet Status code `200` will be returned on success. diff --git a/doc/api/users.md b/doc/api/users.md index c116144d91e5fdacd8a4139649d9aba0099aedbb..200c0e06e0469cbccf76c19341be138619a75a07 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -10,6 +10,7 @@ GET /users [ { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -23,6 +24,7 @@ GET /users }, { "id": 2, + "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", "blocked": false, @@ -52,6 +54,7 @@ Parameters: ```json { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, @@ -75,7 +78,8 @@ POST /users Parameters: + `email` (required) - Email + `password` (required) - Password -+ `name` - Name ++ `username` (required) - Username ++ `name` (required) - Name + `skype` - Skype ID + `linkedin` - Linkedin + `twitter` - Twitter account @@ -95,6 +99,7 @@ GET /user ```json { "id": 1, + "username": "john_smith", "email": "john@example.com", "name": "John Smith", "blocked": false, diff --git a/doc/development.md b/doc/development.md deleted file mode 100644 index b7213adc1e0811151ed656d007f46a3c60e9179d..0000000000000000000000000000000000000000 --- a/doc/development.md +++ /dev/null @@ -1,36 +0,0 @@ -## Development tips: - - -### Installation - -Install the Gitlab development in a virtual machine with the [Gitlab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm). Installing it in a virtual machine makes it much easier to set up all the dependencies for integration testing. - - -### Start application in development mode - -#### 1. Via foreman - - bundle exec foreman start -p 3000 - -#### 2. Manually - - bundle exec rails s - bundle exec rake environment resque:work QUEUE=* VVERBOSE=1 - - -### Test DB setup & seed - - bundle exec rake db:setup RAILS_ENV=test - bundle exec rake db:seed_fu RAILS_ENV=test - - -### Run the Tests - - # All in one - bundle exec rake gitlab:test - - # Rspec - bundle exec rake spec - - # Spinach - bundle exec rake spinach diff --git a/doc/install/databases.md b/doc/install/databases.md index b7beff26a0fdcd55e0addaccd1a28a430651f581..4c6c084d0b94852587d75e86566f23235a7d96c5 100644 --- a/doc/install/databases.md +++ b/doc/install/databases.md @@ -1,71 +1,51 @@ -# Databases: +# Setup Database -GitLab use mysql as default database but you are free to use PostgreSQL or SQLite. +GitLab supports the following databases: +* MySQL (preferred) +* PostgreSQL -## SQLite - - sudo apt-get install -y sqlite3 libsqlite3-dev ## MySQL + # Install the database packages sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev # Login to MySQL $ mysql -u root -p + # Create a user for GitLab. (change $password to a real password) + mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password'; + # Create the GitLab production database mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; - # Create the MySQL User change $password to a real password - mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password'; - - # Grant proper permissions to the MySQL User + # Grant the GitLab user necessary permissopns on the table. mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost'; + # Quit the database session + mysql> \q + + # Try connecting to the new database with the new user + sudo -u gitlab -H mysql -u gitlab -p -D gitlabhq_production ## PostgreSQL - sudo apt-get install -y postgresql-9.1 postgresql-server-dev-9.1 + # Install the database packages + sudo apt-get install -y postgresql-9.1 libpq-dev - # Connect to database server + # Login to PostgreSQL sudo -u postgres psql -d template1 - # Add a user called gitlab. Change $password to a real password + # Create a user for GitLab. (change $password to a real password) template1=# CREATE USER gitlab WITH PASSWORD '$password'; # Create the GitLab production database & grant all privileges on database template1=# CREATE DATABASE gitlabhq_production OWNER gitlab; - # Quit from PostgreSQL server + # Quit the database session template1=# \q - # Try connect to new database - sudo -u gitlab psql -d gitlabhq_production - - - -#### Select the database you want to use - - # SQLite - sudo -u gitlab cp config/database.yml.sqlite config/database.yml - - # Mysql - sudo -u gitlab cp config/database.yml.mysql config/database.yml - - # PostgreSQL - sudo -u gitlab cp config/database.yml.postgresql config/database.yml - - # make sure to update username/password in config/database.yml - -#### Install gems - - # mysql - sudo -u gitlab -H bundle install --without development test sqlite postgres --deployment - - # or postgres - sudo -u gitlab -H bundle install --without development test sqlite mysql --deployment - - # or sqlite - sudo -u gitlab -H bundle install --without development test mysql postgres --deployment + # Try connecting to the new database with the new user + sudo -u gitlab -H psql -d gitlabhq_production diff --git a/doc/install/installation.md b/doc/install/installation.md index 07ed0b0f9de2e7241e4cd34d2ecc0c73f77daee0..718e4cf6419ad367b1d96b8fcdbc1b12115865ea 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -1,283 +1,367 @@ -_This installation guide created for Debian/Ubuntu and properly tested._ +This installation guide was created for Debian/Ubuntu and tested on it. -_Checkout requirements before setup_ +Please read `doc/install/requirements.md` for hardware and platform requirements. -### IMPORTANT +**Important Note:** +The following steps have been known to work. +If you deviate from this guide, do it with caution and make sure you don't +violate any assumptions GitLab makes about its environment. +For things like AWS installation scripts, init scripts or config files for +alternative web server have a look at the "Advanced Setup Tips" section. -Please make sure you have followed all the steps below before posting to the mailing list with installation and configuration questions. -Only create a GitHub Issue if you want a specific part of this installation guide updated. - -Also read the [Read this before you submit an issue](https://github.com/gitlabhq/gitlabhq/wiki/Read-this-before-you-submit-an-issue) wiki page. +**Important Note:** +If you find a bug/error in this guide please submit an issue or pull request +following the contribution guide (see `CONTRIBUTING.md`). - - - -# Basic setup - -The basic installation will provide you a GitLab setup with options: +# Overview -1. ruby 1.9.3 -2. mysql as main db -3. gitolite v3 fork by gitlab -4. nginx + unicorn +The GitLab installation consists of setting up th following components: -The installation consists of next steps: - -1. Packages / dependencies +1. Packages / Dependencies 2. Ruby -3. Users +3. System Users 4. Gitolite -5. Mysql -6. GitLab. -7. Nginx +5. Database +6. GitLab +7. Nginx -# 1. Packages / dependencies +# 1. Packages / Dependencies -*Keep in mind that `sudo` is not installed on Debian by default. You should install it as root:* +`sudo` is not installed on Debian by default. If you don't have it you'll need +to install it first. + # run as root apt-get update && apt-get upgrade && apt-get install sudo -Now install the required packages: +Make sure your system is up-to-date: sudo apt-get update sudo apt-get upgrade - sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libreadline6-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev postfix libpq-dev +**Note:** +Vim is an editor that is used here whenever there are files that need to be +edited by hand. But, you can use any editor you like instead. + + # Install vim + sudo apt-get install -y vim + +Install the required packages: + + sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev wget curl git-core openssh-server redis-server postfix checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev + +Make sure you have the right version of Python installed. + + # Install Python + sudo apt-get install python - sudo pip install pygments + # Make sure that Python is 2.5+ (3.x is not supported at the moment) + python --version + # If it's Python 3 you might need to install Python 2 separately + sudo apt-get install python2.7 -# 2. Install Ruby + # Make sure you can access Python via python2 + python2 --version - wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p194.tar.gz - tar xfvz ruby-1.9.3-p194.tar.gz - cd ruby-1.9.3-p194 + # If you get a "command not found" error create a link to the python binary + sudo ln -s /usr/bin/python /usr/bin/python2 + + +# 2. Ruby + +Download and compile it: + + mkdir /tmp/ruby && cd /tmp/ruby + wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p327.tar.gz + tar xfvz ruby-1.9.3-p327.tar.gz + cd ruby-1.9.3-p327 ./configure make sudo make install -# 3. Users +Install the Bundler Gem: + + sudo gem install bundler + -Create user for git: +# 3. System Users + +Create a user for Git and Gitolite: sudo adduser \ --system \ --shell /bin/sh \ - --gecos 'git version control' \ + --gecos 'Git Version Control' \ --group \ --disabled-password \ --home /home/git \ git -Create user for GitLab: - - # ubuntu/debian - sudo adduser --disabled-login --gecos 'gitlab system' gitlab +Create a user for GitLab: -Add your users to groups: + sudo adduser --disabled-login --gecos 'GitLab' gitlab + # Add it to the git group sudo usermod -a -G git gitlab - sudo usermod -a -G gitlab git -Generate key: - - sudo -H -u gitlab ssh-keygen -q -N '' -t rsa -f /home/gitlab/.ssh/id_rsa + # Generate the SSH key + sudo -u gitlab -H ssh-keygen -q -N '' -t rsa -f /home/gitlab/.ssh/id_rsa # 4. Gitolite Clone GitLab's fork of the Gitolite source code: - sudo -H -u git git clone -b gl-v304 https://github.com/gitlabhq/gitolite.git /home/git/gitolite + cd /home/git + sudo -u git -H git clone -b gl-v320 https://github.com/gitlabhq/gitolite.git /home/git/gitolite -Setup: +Setup Gitolite with GitLab as its admin: - cd /home/git - sudo -u git -H mkdir bin - sudo -u git sh -c 'echo -e "PATH=\$PATH:/home/git/bin\nexport PATH" >> /home/git/.profile' - sudo -u git sh -c 'gitolite/install -ln /home/git/bin' +**Important Note:** +GitLab assumes *full and unshared* control over this Gitolite installation. + + # Add Gitolite scripts to $PATH + sudo -u git -H mkdir /home/git/bin + sudo -u git -H sh -c 'printf "%b\n%b\n" "PATH=\$PATH:/home/git/bin" "export PATH" >> /home/git/.profile' + sudo -u git -H sh -c 'gitolite/install -ln /home/git/bin' + # Copy the gitlab user's (public) SSH key ... sudo cp /home/gitlab/.ssh/id_rsa.pub /home/git/gitlab.pub sudo chmod 0444 /home/git/gitlab.pub + # ... and use it as the admin key for the Gitolite setup sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gitolite setup -pk /home/git/gitlab.pub" - -Permissions: +Fix the directory permissions for the configuration directory: + + # Make sure the Gitolite config dir is owned by git + sudo chmod 750 /home/git/.gitolite/ + sudo chown -R git:git /home/git/.gitolite/ - sudo chmod -R g+rwX /home/git/repositories/ +Fix the directory permissions for the repositories: + + # Make sure the repositories dir is owned by git and it stays that way + sudo chmod -R ug+rwXs,o-rwx /home/git/repositories/ sudo chown -R git:git /home/git/repositories/ - # clone admin repo to add localhost to known_hosts - # & be sure your user has access to gitolite + +## Disable StrictHostKeyChecking for localhost and your domain + + echo "Host localhost + StrictHostKeyChecking no + UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config + + echo "Host YOUR_DOMAIN_NAME + StrictHostKeyChecking no + UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config + + # If gitolite domain differs + echo "Host YOUR_GITOLITE_DOMAIN + StrictHostKeyChecking no + UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config + + +## Test if everything works so far + + # Clone the admin repo so SSH adds localhost to known_hosts ... + # ... and to be sure your users have access to Gitolite sudo -u gitlab -H git clone git@localhost:gitolite-admin.git /tmp/gitolite-admin - # if succeed you can remove it + # If it succeeded without errors you can remove the cloned repo sudo rm -rf /tmp/gitolite-admin -**IMPORTANT! If you can't clone `gitolite-admin` repository - DO NOT PROCEED WITH INSTALLATION** +**Important Note:** +If you can't clone the `gitolite-admin` repository: **DO NOT PROCEED WITH INSTALLATION**! Check the [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) -and ensure you have followed all of the above steps carefully. +and make sure you have followed all of the above steps carefully. -# 5. Mysql database +# 5. Database - sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev +See `doc/install/databases.md` - # Login to MySQL - $ mysql -u root -p - # Create the GitLab production database - mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; +# 6. GitLab - # Create the MySQL User change $password to a real password - mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password'; + # We'll install GitLab into home directory of the user "gitlab" + cd /home/gitlab - # Grant proper permissions to the MySQL User - mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost'; +## Clone the Source + # Clone GitLab repository + sudo -u gitlab -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab -# 6. GitLab + # Go to gitlab dir + cd /home/gitlab/gitlab + + # Checkout to stable release + sudo -u gitlab -H git checkout 4-0-stable - cd /home/gitlab +**Note:** +You can change `4-0-stable` to `master` if you want the *bleeding edge* version, but +do so with caution! + +## Configure it + cd /home/gitlab/gitlab -#### Get source code + # Copy the example GitLab config + sudo -u gitlab -H cp config/gitlab.yml.example config/gitlab.yml - # Get gitlab code. Use this for stable setup - sudo -H -u gitlab git clone -b stable https://github.com/gitlabhq/gitlabhq.git gitlab + # Make sure to change "localhost" to the fully-qualified domain name of your + # host serving GitLab where necessary + sudo -u gitlab -H vim config/gitlab.yml - # Skip this for stable setup. - # Master branch (recent changes, less stable) - sudo -H -u gitlab git clone -b master https://github.com/gitlabhq/gitlabhq.git gitlab + # Make sure GitLab can write to the log/ and tmp/ directories + sudo chown -R gitlab log/ + sudo chown -R gitlab tmp/ + sudo chmod -R u+rwX log/ + sudo chmod -R u+rwX tmp/ + # Copy the example Unicorn config + sudo -u gitlab -H cp config/unicorn.rb.example config/unicorn.rb -#### Copy configs - - cd gitlab +**Important Note:** +Make sure to edit both files to match your setup. - # Rename config files - # - sudo -u gitlab cp config/gitlab.yml.example config/gitlab.yml +## Configure GitLab DB settings - # Copy mysql db config - # - # make sure to update username/password in config/database.yml - # + # Mysql sudo -u gitlab cp config/database.yml.mysql config/database.yml - # Copy unicorn config - # - sudo -u gitlab cp config/unicorn.rb.example config/unicorn.rb + # PostgreSQL + sudo -u gitlab cp config/database.yml.postgresql config/database.yml + +Make sure to update username/password in config/database.yml. -#### Install gems +## Install Gems cd /home/gitlab/gitlab sudo gem install charlock_holmes --version '0.6.9' - sudo gem install bundler - sudo -u gitlab -H bundle install --without development test sqlite postgres --deployment -#### Configure git client + # For mysql db + sudo -u gitlab -H bundle install --deployment --without development test postgres -Gitlab needs to be able to commit and push changes to gitolite. -Git requires a username and email in order to be able to do that. + # Or For postgres db + sudo -u gitlab -H bundle install --deployment --without development test mysql - sudo -u gitlab -H git config --global user.email "gitlab@localhost" - sudo -u gitlab -H git config --global user.name "Gitlab" +## Configure Git -#### Setup application - - sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production +GitLab needs to be able to commit and push changes to Gitolite. In order to do +that Git requires a username and email. (We recommend using the same address +used for the `email.from` setting in `config/gitlab.yml`) + sudo -u gitlab -H git config --global user.name "GitLab" + sudo -u gitlab -H git config --global user.email "gitlab@localhost" -#### Setup GitLab hooks +## Setup GitLab Hooks sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive sudo chown git:git /home/git/.gitolite/hooks/common/post-receive -#### Check application status +## Initialise Database and Activate Advanced Features + + sudo -u gitlab -H bundle exec rake gitlab:app:setup RAILS_ENV=production -Checking status: - sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production +## Check Application Status +Check if GitLab and its environment is configured correctly: - # OUTPUT EXAMPLE - Starting diagnostic - config/database.yml............exists - config/gitlab.yml............exists - /home/git/repositories/............exists - /home/git/repositories/ is writable?............YES - remote: Counting objects: 603, done. - remote: Compressing objects: 100% (466/466), done. - remote: Total 603 (delta 174), reused 0 (delta 0) - Receiving objects: 100% (603/603), 53.29 KiB, done. - Resolving deltas: 100% (174/174), done. - Can clone gitolite-admin?............YES - UMASK for .gitolite.rc is 0007? ............YES - /home/git/share/gitolite/hooks/common/post-receive exists? ............YES + sudo -u gitlab -H bundle exec rake gitlab:env:info RAILS_ENV=production -If you got all YES - congratulations! You can run a GitLab app. +To make sure you didn't miss anything run a more thorough check with: -#### init script + sudo -u gitlab -H bundle exec rake gitlab:check RAILS_ENV=production -Create init script in /etc/init.d/gitlab: +If you are all green: congratulations, you successfully installed GitLab! +Although this is the case, there are still a few steps to go. + + +## Install Init Script + +Download the init script (will be /etc/init.d/gitlab): sudo wget https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab -P /etc/init.d/ sudo chmod +x /etc/init.d/gitlab -GitLab autostart: +Make GitLab start on boot: sudo update-rc.d gitlab defaults 21 -#### Now you should start GitLab application: + +Start your GitLab instance: sudo service gitlab start + # or + sudo /etc/init.d/gitlab restart # 7. Nginx - # Install first +**Note:** +If you can't or don't want to use Nginx as your web server, have a look at the +"Advanced Setup Tips" section. + +## Installation sudo apt-get install nginx - # Add GitLab to nginx sites & change with your host specific settings +## Site Configuration + +Download an example site config: + sudo wget https://raw.github.com/gitlabhq/gitlab-recipes/master/nginx/gitlab -P /etc/nginx/sites-available/ sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab +Make sure to edit the config file to match your setup: + # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** # to the IP address and fully-qualified domain name - # of the host serving GitLab. + # of your host serving GitLab sudo vim /etc/nginx/sites-enabled/gitlab - # Restart nginx: +## Restart + sudo /etc/init.d/nginx restart -# Done! Visit YOUR_SERVER for gitlab instance +# Done! -You can login via web using admin generated with setup: +Visit YOUR_SERVER for your first GitLab login. +The setup has created an admin account for you. You can use it to log in: admin@local.host 5iveL!fe +**Important Note:** +Please go over to your profile page and immediately chage the password, so +nobody can access your GitLab by using this login information later on. -- - - +**Enjoy!** -# Advanced setup tips: +- - - -_Checkout databases.md for postgres or sqlite_ -## Customizing Resque's Redis connection +# Advanced Setup Tips + +## Custom Redis Connection If you'd like Resque to connect to a Redis server on a non-standard port or on -a different host, you can configure its connection string in the -**config/resque.yml** file: +a different host, you can configure its connection string via the +`config/resque.yml` file. + + # example + production: redis.example.tld:6379 + - production: redis.example.com:6379 +## User-contributed Configurations -**Ok - we have a working application now. ** -**But keep going - there are some things that should be done ** +You can find things like AWS installation scripts, init scripts or config files +for alternative web server in our [recipes collection](https://github.com/gitlabhq/gitlab-recipes/). diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 75b02d6456f8392623f7340896cca761ba6bd511..ec5b013c5d8ada34b3b694f9958779ea5c60a021 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -1,28 +1,56 @@ -## Platform requirements: +# Hardware -**The project is designed for the Linux operating system.** +We recommend you to run GitLab on a server with at least 1GB RAM. -It may work on FreeBSD and Mac OS, but we don't test our application for these systems and can't guarantee stability and full functionality. +The necessary hard disk space largely depends on the size of the repos you want +to use GitLab with. But as a *rule of thumb* you should have at least as much +free space as your all repos combined take up. -We officially support (recent versions of) these Linux distributions: + + +# Operating Systems + +## Linux + +GitLab is developed for the Linux operating system. + +GitLab officially supports (recent versions of) these Linux distributions: - Ubuntu Linux - Debian/GNU Linux -It should work on: +It should also work on (though they are not officially supported): +- Arch +- CentOS - Fedora -- CentOs +- Gentoo - RedHat -You might have some luck using these, but no guarantees: +## Other Unix Systems + +There is nothing that prevents GitLab from running on other Unix operating +systems. This means you may get it to work on systems running FreeBSD or OS X. +**If you want to try, please proceed with caution!** + +## Windows + +GitLab does **not** run on Windows and we have no plans of supporting it in the +near future. + + + +# Rubies -- FreeBSD will likely work, see https://github.com/gitlabhq/gitlabhq/issues/796 -- MacOS X will likely work, see https://groups.google.com/forum/#!topic/gitlabhq/5IXHbPkjKLA +GitLab requires Ruby (MRI) 1.9.3 and several Gems with native components. +While it is generally possible to use other Rubies (like +[JRuby](http://jruby.org/) or [Rubinius](http://rubini.us/)) it might require +some work on your part. -GitLab does **not** run on Windows and we have no plans of making GitLab compatible. -## Hardware: +# Installation troubles and reporting success or failure -We recommend to use server with at least 1GB RAM for gitlab instance. +If you have troubles installing GitLab following the official installation guide +or want to share your experience installing GitLab on a not officially supported +platform, please follow the the contribution guide (see CONTRIBUTING.md). diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md new file mode 100644 index 0000000000000000000000000000000000000000..bbfeeb716fac9c8d9861180cbc218a13f62345cc --- /dev/null +++ b/doc/raketasks/backup_restore.md @@ -0,0 +1,82 @@ +### Create a backup of the GitLab system + +Creates a backup archive of the database and all repositories. This archive will be saved in backup_path (see `config/gitlab.yml`). +The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. + +``` +bundle exec rake gitlab:backup:create +``` + +Example output: + +``` +Dumping database tables: +- Dumping table events... [DONE] +- Dumping table issues... [DONE] +- Dumping table keys... [DONE] +- Dumping table merge_requests... [DONE] +- Dumping table milestones... [DONE] +- Dumping table namespaces... [DONE] +- Dumping table notes... [DONE] +- Dumping table projects... [DONE] +- Dumping table protected_branches... [DONE] +- Dumping table schema_migrations... [DONE] +- Dumping table services... [DONE] +- Dumping table snippets... [DONE] +- Dumping table taggings... [DONE] +- Dumping table tags... [DONE] +- Dumping table users... [DONE] +- Dumping table users_projects... [DONE] +- Dumping table web_hooks... [DONE] +- Dumping table wikis... [DONE] +Dumping repositories: +- Dumping repository abcd... [DONE] +- Dumping repository gitolite-admin.git... [DONE] +Creating backup archive: $TIMESTAMP_gitlab_backup.tar [DONE] +Deleting tmp directories...[DONE] +Deleting old backups... [SKIPPING] +``` + +### Restore a previously created backup + +``` +bundle exec rake gitlab:backup:restore +``` + +Options: + +``` +BACKUP=timestamp_of_backup (required if more than one backup exists) +``` + +Example output: + +``` +Unpacking backup... [DONE] +Restoring database tables: +-- create_table("events", {:force=>true}) + -> 0.2231s +[...] +- Loading fixture events...[DONE] +- Loading fixture issues...[DONE] +- Loading fixture keys...[SKIPPING] +- Loading fixture merge_requests...[DONE] +- Loading fixture milestones...[DONE] +- Loading fixture namespaces...[DONE] +- Loading fixture notes...[DONE] +- Loading fixture projects...[DONE] +- Loading fixture protected_branches...[SKIPPING] +- Loading fixture schema_migrations...[DONE] +- Loading fixture services...[SKIPPING] +- Loading fixture snippets...[SKIPPING] +- Loading fixture taggings...[SKIPPING] +- Loading fixture tags...[SKIPPING] +- Loading fixture users...[DONE] +- Loading fixture users_projects...[DONE] +- Loading fixture web_hooks...[SKIPPING] +- Loading fixture wikis...[SKIPPING] +Restoring repositories: +- Restoring repository abcd... [DONE] +- Restoring repository gitolite-admin.git... [DONE] +Deleting tmp directories...[DONE] +``` diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md new file mode 100644 index 0000000000000000000000000000000000000000..7a2a4b668bd0e2852baad1fa52983c6b8d93829e --- /dev/null +++ b/doc/raketasks/features.md @@ -0,0 +1,36 @@ +### Enable usernames and namespaces for user projects + +This command will enable the namespaces feature introduced in v4.0. It will move every project in its namespace folder. + +Note: + +* Because the **repository location will change**, you will need to **update all your git url's** to point to the new location. +* Username can be changed at [Profile / Account](/profile/account) + +**Example:** + +Old path: `git@example.org:myrepo.git` +New path: `git@example.org:username/myrepo.git` or `git@example.org:groupname/myrepo.git` + +``` +bundle exec rake gitlab:enable_namespaces +``` + + +### Enable auto merge + +This command will enable the auto merge feature. After this you will be able to **merge a merge request** via GitLab and use the **online editor**. + +``` +bundle exec rake gitlab:enable_automerge +``` + +Example output: + +``` +Creating satellite for abcd.git +[git clone output] +Creating satellite for abcd2.git +[git clone output] +done +``` diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md new file mode 100644 index 0000000000000000000000000000000000000000..bb8e1ed29f70958583ecc506faad9899b3ae57fe --- /dev/null +++ b/doc/raketasks/maintenance.md @@ -0,0 +1,191 @@ +### Setup production application + +Runs the following rake tasks: + +* db:setup (Create the database, load the schema, and initialize with the seed data) +* db:seed_fu (Loads seed data for the current environment.) +* gitlab:app:enable_automerge (see "Features") + +``` +bundle exec rake gitlab:app:setup +``` + + +### Gather information about GitLab and the system it runs on + +This command gathers information about your GitLab installation and the System +it runs on. These may be useful when asking for help or reporting issues. + +``` +bundle exec rake gitlab:env:info +``` + +Example output: + +``` +System information +System: Debian 6.0.6 +Current User: gitlab +Using RVM: yes +RVM Version: 1.17.2 +Ruby Version: ruby-1.9.3-p327 +Gem Version: 1.8.24 +Bundler Version:1.2.3 +Rake Version: 10.0.1 + +GitLab information +Version: 3.1.0 +Resivion: fd5141d +Directory: /home/gitlab/gitlab +DB Adapter: mysql2 +URL: http://localhost:3000 +HTTP Clone URL: http://localhost:3000/some-project.git +SSH Clone URL: git@localhost:some-project.git +Using LDAP: no +Using Omniauth: no + +Gitolite information +Version: v3.04-4-g4524f01 +Admin URI: git@localhost:gitolite-admin +Admin Key: gitlab +Repositories: /home/git/repositories/ +Hooks: /home/git/.gitolite/hooks/ +Git: /usr/bin/git +``` + + +### Check GitLab configuration + +Runs the following rake tasks: + +* gitlab:env:check +* gitlab:gitolite:check +* gitlab:resque:check +* gitlab:app:check + +It will check that each component was setup according to the installation guide and suggest fixes for issues found. + +You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide). + +``` +bundle exec rake gitlab:check +``` + +Example output: + +``` +Checking Environment ... + +gitlab user is in git group? ... yes +Has no "-e" in ~git/.profile ... yes +Git configured for gitlab user? ... yes +Has python2? ... yes +python2 is supported version? ... yes + +Checking Environment ... Finished + +Checking Gitolite ... + +Using recommended version ... yes +Repo umask is 0007 in .gitolite.rc? ... yes +Allow all Git config keys in .gitolite.rc ... yes +Config directory exists? ... yes +Config directory owned by git:git? ... yes +Config directory access is drwxr-x---? ... yes +Repo base directory exists? ... yes +Repo base owned by git:git? ... yes +Repo base access is drwsrws---? ... yes +Can clone gitolite-admin? ... yes +Can commit to gitolite-admin? ... yes +post-receive hook exists? ... yes +post-receive hook up-to-date? ... yes +post-receive hooks in repos are links: ... +GitLab ... ok +Non-Ascii Files Test ... ok +Touch Commit Test ... ok +Without Master Test ... ok +Git config in repos: ... +GitLab ... ok +Non-Ascii Files Test ... ok +Touch Commit Test ... ok +Without Master Test ... ok + +Checking Gitolite ... Finished + +Checking Resque ... + +Running? ... yes + +Checking Resque ... Finished + +Checking GitLab ... + +Database config exists? ... yes +Database is not SQLite ... yes +All migrations up? ... yes +GitLab config exists? ... yes +GitLab config not outdated? ... yes +Log directory writable? ... yes +Tmp directory writable? ... yes +Init script exists? ... yes +Init script up-to-date? ... yes +Projects have satellites? ... +GitLab ... yes +Non-Ascii Files Test ... yes +Touch Commit Test ... yes +Without Master Test ... yes + +Checking GitLab ... Finished +``` + + +### (Re-)Create satellite repos + +This will create satellite repos for all your projects. +If necessary, remove the `tmp/repo_satellites` directory and rerun the command below. + +``` +bundle exec rake gitlab:satellites:create +``` + + +### Rebuild each key at gitolite config + +This will send all users ssh public keys to gitolite and grant them access (based on their permission) to their projects. + +``` +bundle exec rake gitlab:gitolite:update_keys +``` + + +### Rebuild each project at gitolite config + +This makes sure that all projects are present in gitolite and can be accessed. + +``` +bundle exec rake gitlab:gitolite:update_repos +``` + +### Import bare repositories into GitLab project instance + +Notes: + +* project owner will be a first admin +* existing projects will be skipped + +How to use: + +1. copy your bare repos under git base_path (see `config/gitlab.yml` git_host -> base_path) +2. run the command below + +``` +bundle exec rake gitlab:import:repos RAILS_ENV=production +``` + +Example output: + +``` +Processing abcd.git + * Created abcd (abcd.git) +[...] +``` diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md new file mode 100644 index 0000000000000000000000000000000000000000..021ce35931f64c6421be9b7013025b1b19b32365 --- /dev/null +++ b/doc/raketasks/user_management.md @@ -0,0 +1,16 @@ +### Add user to as a developer to all projects + +``` +bundle exec rake gitlab:import:user_to_projects[username@domain.tld] +``` + + +### Add all users to all projects + +Notes: + +* admin users are added as masters + +``` +bundle exec rake gitlab:import:all_users_to_all_projects +``` diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index fce85ce9901198e26035c432c0e7e916cd23672a..226d3d5d5b5b567aec417932b40b648f212faae0 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -12,6 +12,11 @@ Feature: Admin active tab Then the active main tab should be Projects And no other main tabs should be active + Scenario: On Admin Groups + Given I visit admin groups page + Then the active main tab should be Groups + And no other main tabs should be active + Scenario: On Admin Users Given I visit admin users page Then the active main tab should be Users diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 24296f465799935706a3f9d47952d592157a3feb..972f8e3660928a1e6efe7901d21726ab84010d2e 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -15,6 +15,12 @@ Feature: Dashboard And I visit dashboard page Then I should see groups list + Scenario: I should see correct projects count + Given I have group with projects + And group has a projects that does not belongs to me + When I visit dashboard page + Then I should see 1 project at group list + Scenario: I should see last push widget Then I should see last push widget And I click "Create Merge Request" link diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 596e8bd7d4188c7301394a94608ff10487dba0ce..d6ef384c9a63d470d7c9baffb0df275c3edd3c7f 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -24,11 +24,9 @@ Feature: Project Issues Given I click link "Release 0.4" Then I should see issue "Release 0.4" - @javascript Scenario: I submit new unassigned issue Given I click link "New Issue" And I submit new issue "500 error on profile" - Given I click link "500 error on profile" Then I should see issue "500 error on profile" @javascript @@ -57,25 +55,19 @@ Feature: Project Issues Then I should see "Release 0.3" in issues And I should not see "Release 0.4" in issues - @javascript - Scenario: I clear search - Given I click link "All" - And I fill in issue search with "Something" - And I fill in issue search with "" - Then I should see "Release 0.4" in issues - And I should see "Release 0.3" in issues - - @javascript - Scenario: I create Issue with pre-selected milestone - Given project "Shop" has milestone "v2.2" - And project "Shop" has milestone "v3.0" - And I visit project "Shop" issues page - When I select milestone "v3.0" - And I click link "New Issue" - Then I should see selected milestone with title "v3.0" + # Disable this two cause of random failing + # TODO: fix after v4.0 released + #@javascript + #Scenario: I create Issue with pre-selected milestone + #Given project "Shop" has milestone "v2.2" + #And project "Shop" has milestone "v3.0" + #And I visit project "Shop" issues page + #When I select milestone "v3.0" + #And I click link "New Issue" + #Then I should see selected milestone with title "v3.0" - @javascript - Scenario: I create Issue with pre-selected assignee - When I select first assignee from "Shop" project - And I click link "New Issue" - Then I should see first assignee from "Shop" as selected assignee + #@javascript + #Scenario: I create Issue with pre-selected assignee + #When I select first assignee from "Shop" project + #And I click link "New Issue" + #Then I should see first assignee from "Shop" as selected assignee diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 51370565a3b982c8a61d32bc3f134e7f5c224539..f052e2f244c5df8971b89fa75962234cfd3a0b43 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -7,9 +7,3 @@ Feature: Project Wiki Scenario: Add new page Given I create Wiki page Then I should see newly created wiki page - - @javascript - Scenario: I comment wiki page - Given I create Wiki page - And I leave a comment like "XML attached" - Then I should see comment "XML attached" diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/admin_active_tab.rb index 292908928230c5658a691697be679833f16d977a..05a9a686e010b7b0829dee2ec47c0ce2e607af38 100644 --- a/features/steps/admin/admin_active_tab.rb +++ b/features/steps/admin/admin_active_tab.rb @@ -11,6 +11,10 @@ class AdminActiveTab < Spinach::FeatureSteps ensure_active_main_tab('Projects') end + Then 'the active main tab should be Groups' do + ensure_active_main_tab('Groups') + end + Then 'the active main tab should be Users' do ensure_active_main_tab('Users') end diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/admin_groups.rb index e1759013ce763c3706a89b31b0a2bd4d5d6a4b39..5386f47332021e65f5b21e57f660a22258c5ec42 100644 --- a/features/steps/admin/admin_groups.rb +++ b/features/steps/admin/admin_groups.rb @@ -9,8 +9,7 @@ class AdminGroups < Spinach::FeatureSteps And 'submit form with new group info' do fill_in 'group_name', :with => 'gitlab' - fill_in 'group_code', :with => 'gitlab' - click_button "Save group" + click_button "Create group" end Then 'I should see newly created group' do diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 99c48738876089c1b5e53a8e4b0b611d54518cb6..775a721f1a486d8f7b591459f7f9731fa3021315 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -103,4 +103,14 @@ class Dashboard < Spinach::FeatureSteps page.should have_link group.name end end + + And 'group has a projects that does not belongs to me' do + @forbidden_project1 = create(:project, group: @group) + @forbidden_project2 = create(:project, group: @group) + end + + Then 'I should see 1 project at group list' do + page.find('span.last_activity/span').should have_content('1') + end + end diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb index e5caf905f953f58e0c197a1a6f8d963d1e9459c7..5ace88023f0d3f29f995ab7b0d162814e4cfbac2 100644 --- a/features/steps/dashboard/dashboard_issues.rb +++ b/features/steps/dashboard/dashboard_issues.rb @@ -7,6 +7,7 @@ class DashboardIssues < Spinach::FeatureSteps issues.each do |issue| page.should have_content(issue.title[0..10]) page.should have_content(issue.project.name) + page.should have_link(issue.project.name) end end diff --git a/features/steps/project/create_project.rb b/features/steps/project/create_project.rb index 6d2ca3f9b568bc126b738f93728ed6a0b73a4aec..b9b4534ed6874d91a7659fbac528fe5dff99752b 100644 --- a/features/steps/project/create_project.rb +++ b/features/steps/project/create_project.rb @@ -4,8 +4,6 @@ class CreateProject < Spinach::FeatureSteps And 'fill project form with valid data' do fill_in 'project_name', :with => 'NewProject' - fill_in 'project_code', :with => 'NPR' - fill_in 'project_path', :with => 'newproject' click_button "Create project" end diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb index 6bf164e2c8f0079478c8f5956851434926841473..2c03ce14fc199a56f8cd3cc57e9ffe174e2067e3 100644 --- a/features/steps/project/project_browse_commits.rb +++ b/features/steps/project/project_browse_commits.rb @@ -32,8 +32,8 @@ class ProjectBrowseCommits < Spinach::FeatureSteps end And 'I fill compare fields with refs' do - fill_in "from", with: "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" - fill_in "to", with: "8716fc78f3c65bbf7bcf7b574febd583bc5d2812" + fill_in "from", with: "8716fc78f3c65bbf7bcf7b574febd583bc5d2812" + fill_in "to", with: "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" click_button "Compare" end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb index 88bfac638d0510883c1136d3d3f10a9817960614..2103aeb17158c5140520912e1cb8934226f10dcd 100644 --- a/features/steps/project/project_issues.rb +++ b/features/steps/project/project_issues.rb @@ -73,7 +73,6 @@ class ProjectIssues < Spinach::FeatureSteps end And 'I fill in issue search with ""' do - page.execute_script("$('.issue_search').val('').keyup();"); fill_in 'issue_search', with: "" end @@ -96,7 +95,7 @@ class ProjectIssues < Spinach::FeatureSteps end Then 'I should see selected milestone with title "v3.0"' do - issues_milestone_selector = "#milestone_id_chzn > a" + issues_milestone_selector = "#issue_milestone_id_chzn > a" page.find(issues_milestone_selector).should have_content("v3.0") end @@ -107,7 +106,7 @@ class ProjectIssues < Spinach::FeatureSteps end Then 'I should see first assignee from "Shop" as selected assignee' do - issues_assignee_selector = "#assignee_id_chzn > a" + issues_assignee_selector = "#issue_assignee_id_chzn > a" project = Project.find_by_name "Shop" assignee_name = project.users.first.name page.find(issues_assignee_selector).should have_content(assignee_name) diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 33a940274723d963853e7503ec6b8a765cab7791..a12576288dfa7e2fca7cbce454d44edec613458a 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -54,7 +54,7 @@ module SharedPaths end Given 'I visit profile account page' do - visit profile_account_path + visit account_profile_path end Given 'I visit profile SSH keys page' do @@ -62,15 +62,11 @@ module SharedPaths end Given 'I visit profile design page' do - visit profile_design_path + visit design_profile_path end Given 'I visit profile history page' do - visit profile_history_path - end - - Given 'I visit profile token page' do - visit profile_token_path + visit history_profile_path end # ---------------------------------------- diff --git a/features/support/env.rb b/features/support/env.rb index 1a72d765197beb3e8e3e0bc9326c1b4ff7c89a59..500de0f3e202a1f566e9384dd30eda73a3727492 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -32,7 +32,10 @@ end DatabaseCleaner.strategy = :truncation Spinach.hooks.before_scenario do - DatabaseCleaner.start + # Use tmp dir for FS manipulations + Gitlab.config.gitolite.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path')) + FileUtils.rm_rf Gitlab.config.gitolite.repos_path + FileUtils.mkdir_p Gitlab.config.gitolite.repos_path end Spinach.hooks.after_scenario do diff --git a/lib/api.rb b/lib/api.rb index 7a1845443e734eed6e220f318eb34efd8df33932..f58b82ff98ebb99cfba84e027e573937ca3c4ca6 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -2,8 +2,7 @@ Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} module Gitlab class API < Grape::API - VERSION = 'v2' - version VERSION, using: :path + version 'v3', using: :path rescue_from ActiveRecord::RecordNotFound do rack_response({'message' => '404 Not found'}.to_json, 404) @@ -19,5 +18,6 @@ module Gitlab mount Milestones mount Session mount MergeRequests + mount Notes end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9e605a607a2c92b7d81a545ea01c272b59978d67..e5b2685abf560033c458789f1d0d8c4ea7a5db60 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,12 +1,12 @@ module Gitlab module Entities class User < Grape::Entity - expose :id, :email, :name, :bio, :skype, :linkedin, :twitter, + expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :blocked, :created_at end class UserBasic < Grape::Entity - expose :id, :email, :name, :blocked, :created_at + expose :id, :username, :email, :name, :blocked, :created_at end class UserLogin < UserBasic @@ -14,11 +14,11 @@ module Gitlab end class Hook < Grape::Entity - expose :id, :url + expose :id, :url, :created_at end class Project < Grape::Entity - expose :id, :code, :name, :description, :path, :default_branch + expose :id, :name, :description, :default_branch expose :owner, using: Entities::UserBasic expose :private_flag, as: :private expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at @@ -61,7 +61,7 @@ module Gitlab end class SSHKey < Grape::Entity - expose :id, :title, :key + expose :id, :title, :key, :created_at end class MergeRequest < Grape::Entity @@ -70,8 +70,15 @@ module Gitlab end class Note < Grape::Entity + expose :id + expose :note, as: :body expose :author, using: Entities::UserBasic + expose :created_at + end + + class MRNote < Grape::Entity expose :note + expose :author, using: Entities::UserBasic end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a339ec4a6fc4a9982be38fba9a545e3c37132613..6bd8111c2b20366c3aef1db626eeb152f480a8dd 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -5,13 +5,18 @@ module Gitlab end def user_project - if @project ||= current_user.projects.find_by_id(params[:id]) || - current_user.projects.find_by_code(params[:id]) + @project ||= find_project + @project || not_found! + end + + def find_project + project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id]) + + if project && can?(current_user, :read_project, project) + project else - not_found! + nil end - - @project end def paginate(object) @@ -32,6 +37,10 @@ module Gitlab end end + def can?(object, action, subject) + abilities.allowed?(object, action, subject) + end + def attributes_for_keys(keys) attrs = {} keys.each do |key| diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4ee2d11f15febf8382493141c8c02167bc935d92..3be558816b526d5ed45efd24580227869acb1fcd 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -17,7 +17,7 @@ module Gitlab # Get a list of project issues # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/issues get ":id/issues" do @@ -27,7 +27,7 @@ module Gitlab # Get a single project issue # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # GET /projects/:id/issues/:issue_id @@ -39,7 +39,7 @@ module Gitlab # Create a new project issue # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # title (required) - The title of an issue # description (optional) - The description of an issue # assignee_id (optional) - The ID of a user to assign issue @@ -62,7 +62,7 @@ module Gitlab # Update an existing issue # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # title (optional) - The title of an issue # description (optional) - The description of an issue @@ -88,7 +88,7 @@ module Gitlab # Delete a project issue (deprecated) # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # DELETE /projects/:id/issues/:issue_id diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d8f2c51293a532277a0105176e6bb4c93e3e052b..470cd1e1c2d5cbf22c282181af4e4fd8b536fd34 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -4,35 +4,35 @@ module Gitlab before { authenticate! } resource :projects do - + # List merge requests - # + # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # # Example: # GET /projects/:id/merge_requests # get ":id/merge_requests" do authorize! :read_merge_request, user_project - + present paginate(user_project.merge_requests), with: Entities::MergeRequest end - + # Show MR - # + # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # merge_request_id (required) - The ID of MR - # + # # Example: # GET /projects/:id/merge_request/:merge_request_id # get ":id/merge_request/:merge_request_id" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - + authorize! :read_merge_request, merge_request - + present merge_request, with: Entities::MergeRequest end @@ -40,22 +40,22 @@ module Gitlab # # Parameters: # - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # source_branch (required) - The source branch # target_branch (required) - The target branch # assignee_id - Assignee user ID # title (required) - Title of MR - # + # # Example: # POST /projects/:id/merge_requests # post ":id/merge_requests" do authorize! :write_merge_request, user_project - + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] merge_request = user_project.merge_requests.new(attrs) merge_request.author = current_user - + if merge_request.save merge_request.reload_code present merge_request, with: Entities::MergeRequest @@ -67,7 +67,7 @@ module Gitlab # Update MR # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # merge_request_id (required) - ID of MR # source_branch - The source branch # target_branch - The target branch @@ -80,9 +80,9 @@ module Gitlab put ":id/merge_request/:merge_request_id" do attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] merge_request = user_project.merge_requests.find(params[:merge_request_id]) - + authorize! :modify_merge_request, merge_request - + if merge_request.update_attributes attrs merge_request.reload_code merge_request.mark_as_unchecked @@ -95,10 +95,10 @@ module Gitlab # Post comment to merge request # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # merge_request_id (required) - ID of MR # note (required) - Text of comment - # Examples: + # Examples: # POST /projects/:id/merge_request/:merge_request_id/comments # post ":id/merge_request/:merge_request_id/comments" do @@ -107,7 +107,7 @@ module Gitlab note.author = current_user if note.save - present note, with: Entities::Note + present note, with: Entities::MRNote else not_found! end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index f55dfd04cc5b593c34c241708a5126e8fb876041..6aca9d01b09f41aa49fe7f87868c8cb0f2ca22dd 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -7,7 +7,7 @@ module Gitlab # Get a list of project milestones # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/milestones get ":id/milestones" do @@ -19,7 +19,7 @@ module Gitlab # Get a single project milestone # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # milestone_id (required) - The ID of a project milestone # Example Request: # GET /projects/:id/milestones/:milestone_id @@ -33,7 +33,7 @@ module Gitlab # Create a new project milestone # # Parameters: - # id (required) - The ID or code name of the project + # id (required) - The ID of the project # title (required) - The title of the milestone # description (optional) - The description of the milestone # due_date (optional) - The due date of the milestone @@ -54,7 +54,7 @@ module Gitlab # Update an existing project milestone # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # milestone_id (required) - The ID of a project milestone # title (optional) - The title of a milestone # description (optional) - The description of a milestone diff --git a/lib/api/notes.rb b/lib/api/notes.rb new file mode 100644 index 0000000000000000000000000000000000000000..4875ac4c03e7ce08088347a2cfc570dac8a5fb92 --- /dev/null +++ b/lib/api/notes.rb @@ -0,0 +1,106 @@ +module Gitlab + # Notes API + class Notes < Grape::API + before { authenticate! } + + NOTEABLE_TYPES = [Issue, Snippet] + + resource :projects do + # Get a list of project wall notes + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/notes + get ":id/notes" do + @notes = user_project.common_notes + present paginate(@notes), with: Entities::Note + end + + # Get a single project wall note + # + # Parameters: + # id (required) - The ID of a project + # note_id (required) - The ID of a note + # Example Request: + # GET /projects/:id/notes/:note_id + get ":id/notes/:note_id" do + @note = user_project.common_notes.find(params[:note_id]) + present @note, with: Entities::Note + end + + # Create a new project wall note + # + # Parameters: + # id (required) - The ID of a project + # body (required) - The content of a note + # Example Request: + # POST /projects/:id/notes + post ":id/notes" do + @note = user_project.notes.new(note: params[:body]) + @note.author = current_user + + if @note.save + present @note, with: Entities::Note + else + not_found! + end + end + + NOTEABLE_TYPES.each do |noteable_type| + noteables_str = noteable_type.to_s.underscore.pluralize + noteable_id_str = "#{noteable_type.to_s.underscore}_id" + + # Get a list of project +noteable+ notes + # + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue or snippet + # Example Request: + # GET /projects/:id/issues/:noteable_id/notes + # GET /projects/:id/snippets/:noteable_id/notes + get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + present paginate(@noteable.notes), with: Entities::Note + end + + # Get a single +noteable+ note + # + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue or snippet + # note_id (required) - The ID of a note + # Example Request: + # GET /projects/:id/issues/:noteable_id/notes/:note_id + # GET /projects/:id/snippets/:noteable_id/notes/:note_id + get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.find(params[:note_id]) + present @note, with: Entities::Note + end + + # Create a new +noteable+ note + # + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue or snippet + # body (required) - The content of a note + # Example Request: + # POST /projects/:id/issues/:noteable_id/notes + # POST /projects/:id/snippets/:noteable_id/notes + post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) + @note = @noteable.notes.new(note: params[:body]) + @note.author = current_user + @note.project = user_project + + if @note.save + present @note, with: Entities::Note + else + not_found! + end + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ac20bbeccab9b90cebbf530185be9992f0a3042e..fb01524da39fd91e938c348f58119e461555c539 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -16,7 +16,7 @@ module Gitlab # Get a single project # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id get ":id" do @@ -27,8 +27,6 @@ module Gitlab # # Parameters: # name (required) - name for new project - # code (optional) - code for new project, uses project name if not set - # path (optional) - path for new project, uses project name if not set # description (optional) - short project description # default_branch (optional) - 'master' by default # issues_enabled (optional) - enabled by default @@ -38,11 +36,7 @@ module Gitlab # Example Request # POST /projects post do - params[:code] ||= params[:name] - params[:path] ||= params[:name] - attrs = attributes_for_keys [:code, - :path, - :name, + attrs = attributes_for_keys [:name, :description, :default_branch, :issues_enabled, @@ -60,18 +54,23 @@ module Gitlab # Get a project team members # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project + # query - Query string # Example Request: # GET /projects/:id/members get ":id/members" do - @members = paginate user_project.users + if params[:query].present? + @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%") + else + @members = paginate user_project.users + end present @members, with: Entities::ProjectMember, project: user_project end # Get a project team members # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # user_id (required) - The ID of a user # Example Request: # GET /projects/:id/members/:user_id @@ -83,7 +82,7 @@ module Gitlab # Add a new project team member # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # user_id (required) - The ID of a user # access_level (required) - Project access level # Example Request: @@ -106,7 +105,7 @@ module Gitlab # Update project team member # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # user_id (required) - The ID of a team member # access_level (required) - Project access level # Example Request: @@ -126,7 +125,7 @@ module Gitlab # Remove a team member from project # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # user_id (required) - The ID of a team member # Example Request: # DELETE /projects/:id/members/:user_id @@ -139,7 +138,7 @@ module Gitlab # Get project hooks # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/hooks get ":id/hooks" do @@ -151,7 +150,7 @@ module Gitlab # Get a project hook # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # hook_id (required) - The ID of a project hook # Example Request: # GET /projects/:id/hooks/:hook_id @@ -164,7 +163,7 @@ module Gitlab # Add hook to project # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # url (required) - The hook URL # Example Request: # POST /projects/:id/hooks @@ -181,7 +180,7 @@ module Gitlab # Update an existing project hook # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # hook_id (required) - The ID of a project hook # url (required) - The hook URL # Example Request: @@ -202,7 +201,7 @@ module Gitlab # Delete project hook # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # hook_id (required) - The ID of hook to delete # Example Request: # DELETE /projects/:id/hooks @@ -215,7 +214,7 @@ module Gitlab # Get a project repository branches # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/repository/branches get ":id/repository/branches" do @@ -225,7 +224,7 @@ module Gitlab # Get a single branch # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # branch (required) - The name of the branch # Example Request: # GET /projects/:id/repository/branches/:branch @@ -237,7 +236,7 @@ module Gitlab # Get a project repository tags # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do @@ -247,7 +246,7 @@ module Gitlab # Get a project repository commits # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # ref_name (optional) - The name of a repository branch or tag # Example Request: # GET /projects/:id/repository/commits @@ -265,7 +264,7 @@ module Gitlab # Get a project snippets # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # Example Request: # GET /projects/:id/snippets get ":id/snippets" do @@ -275,7 +274,7 @@ module Gitlab # Get a project snippet # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # snippet_id (required) - The ID of a project snippet # Example Request: # GET /projects/:id/snippets/:snippet_id @@ -287,7 +286,7 @@ module Gitlab # Create a new project snippet # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # title (required) - The title of a snippet # file_name (required) - The name of a snippet file # lifetime (optional) - The expiration date of a snippet @@ -313,7 +312,7 @@ module Gitlab # Update an existing project snippet # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # snippet_id (required) - The ID of a project snippet # title (optional) - The title of a snippet # file_name (optional) - The name of a snippet file @@ -339,7 +338,7 @@ module Gitlab # Delete a project snippet # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # snippet_id (required) - The ID of a project snippet # Example Request: # DELETE /projects/:id/snippets/:snippet_id @@ -353,7 +352,7 @@ module Gitlab # Get a raw project snippet # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # snippet_id (required) - The ID of a project snippet # Example Request: # GET /projects/:id/snippets/:snippet_id/raw @@ -366,7 +365,7 @@ module Gitlab # Get a raw file contents # # Parameters: - # id (required) - The ID or code name of a project + # id (required) - The ID of a project # sha (required) - The commit or branch name # filepath (required) - The path to the file to display # Example Request: diff --git a/lib/api/users.rb b/lib/api/users.rb index 57e0aa108cfb8cd8dc78f7b869f0a75bdf9bfddc..140c20f6bd21cb8e6cb54e3d5852b8d409074aac 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -38,7 +38,7 @@ module Gitlab # POST /users post do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username] user = User.new attrs, as: :admin if user.save present user, with: Entities::User @@ -101,8 +101,6 @@ module Gitlab key = current_user.keys.find params[:id] key.delete end - - end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 5a24c5d01b510887cc7cfb38afbcaf97302e561d..8c45c93557e7bf4b346ebe8b287830223a9f1635 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -34,10 +34,11 @@ module Gitlab extern_uid: uid, provider: provider, name: name, + username: email.match(/^[^@]*/)[0], email: email, password: password, password_confirmation: password, - projects_limit: Gitlab.config.default_projects_limit, + projects_limit: Gitlab.config.gitlab.default_projects_limit, }, as: :admin) if Gitlab.config.omniauth['block_auto_created_users'] && !ldap @user.blocked = true diff --git a/lib/gitlab/backend/gitolite.rb b/lib/gitlab/backend/gitolite.rb index fe5dcef40a9fd5543fe0d97162e94f6e654ca5ba..3b8a2090f737a072d1fe9c984e609a842bdaa836 100644 --- a/lib/gitlab/backend/gitolite.rb +++ b/lib/gitlab/backend/gitolite.rb @@ -23,7 +23,14 @@ module Gitlab end def update_repository project - config.update_project!(project.path, project) + config.update_project!(project) + end + + def move_repository(old_repo, project) + config.apply do |config| + config.clean_repo(old_repo) + config.update_project(project) + end end def remove_repository project @@ -31,13 +38,19 @@ module Gitlab end def url_to_repo path - Gitlab.config.ssh_path + "#{path}.git" + Gitlab.config.gitolite.ssh_path_prefix + "#{path}.git" end def enable_automerge config.admin_all_repo! end + def update_repositories projects + config.apply do |config| + config.update_projects(projects) + end + end + alias_method :create_repository, :update_repository end end diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb index 7ae34de66bc39b1148435d56e362dc2809482dcb..a2bc4ca8d74f662ebf56acf87b7b1c77077c9c24 100644 --- a/lib/gitlab/backend/gitolite_config.rb +++ b/lib/gitlab/backend/gitolite_config.rb @@ -16,7 +16,7 @@ module Gitlab def ga_repo @ga_repo ||= ::Gitolite::GitoliteAdmin.new( File.join(config_tmp_dir,'gitolite'), - conf: Gitlab.config.gitolite_config_file + conf: Gitlab.config.gitolite.config_file ) end @@ -83,7 +83,11 @@ module Gitlab def destroy_project(project) FileUtils.rm_rf(project.path_to_repo) - conf.rm_repo(project.path) + conf.rm_repo(project.path_with_namespace) + end + + def clean_repo repo_name + conf.rm_repo(repo_name) end def destroy_project!(project) @@ -105,18 +109,18 @@ module Gitlab end # update or create - def update_project(repo_name, project) + def update_project(project) repo = update_project_config(project, conf) conf.add_repo(repo, true) end - def update_project!(repo_name, project) + def update_project!( project) apply do |config| - config.update_project(repo_name, project) + config.update_project(project) end end - # Updates many projects and uses project.path as the repo path + # Updates many projects and uses project.path_with_namespace as the repo path # An order of magnitude faster than update_project def update_projects(projects) projects.each do |project| @@ -126,7 +130,7 @@ module Gitlab end def update_project_config(project, conf) - repo_name = project.path + repo_name = project.path_with_namespace repo = if conf.has_repo?(repo_name) conf.get_repo(repo_name) @@ -163,7 +167,7 @@ module Gitlab # Enable access to all repos for gitolite admin. # We use it for accept merge request feature def admin_all_repo - owner_name = Gitlab.config.gitolite_admin_key + owner_name = Gitlab.config.gitolite.admin_key # @ALL repos premission for gitolite owner repo_name = "@all" @@ -185,7 +189,7 @@ module Gitlab def pull tmp_dir Dir.mkdir tmp_dir - `git clone #{Gitlab.config.gitolite_admin_uri} #{tmp_dir}/gitolite` + `git clone #{Gitlab.config.gitolite.admin_uri} #{tmp_dir}/gitolite` unless File.exists?(File.join(tmp_dir, 'gitolite', 'conf', 'gitolite.conf')) raise PullError, "unable to clone gitolite-admin repo" diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index dd5a9becafc386c0644a0be302b65af4f772f42f..7c31117f01d9e83f3cc47db5612c48294d1af33b 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -4,10 +4,14 @@ module Grack def valid? # Authentication with username and password - email, password = @auth.credentials - self.user = User.find_by_email(email) + login, password = @auth.credentials + + self.user = User.find_by_email(login) || User.find_by_username(login) + return false unless user.try(:valid_password?, password) + email = user.email + # Set GL_USER env variable ENV['GL_USER'] = email # Pass Gitolite update hook @@ -18,8 +22,8 @@ module Grack @env['SCRIPT_NAME'] = "" # Find project by PATH_INFO from env - if m = /^\/([\w\.-]+)\.git/.match(@request.path_info).to_a - self.project = Project.find_by_path(m.last) + if m = /^\/([\w\.\/-]+)\.git/.match(@request.path_info).to_a + self.project = Project.find_with_namespace(m.last) return false unless project end @@ -34,12 +38,12 @@ module Grack end def validate_get_request - true + can?(user, :download_code, project) end def validate_post_request if @request.path_info.end_with?('git-upload-pack') - can?(user, :push_code, project) + can?(user, :download_code, project) elsif @request.path_info.end_with?('git-receive-pack') action = if project.protected_branch?(current_ref) :push_code_to_protected_branches diff --git a/lib/gitlab/graph/commit.rb b/lib/gitlab/graph/commit.rb index af8d7828ce95f8c0ea35a7e1a3ca78346354ad48..3d82c34432f53aa7e5213b5e16eef53348d4b33a 100644 --- a/lib/gitlab/graph/commit.rb +++ b/lib/gitlab/graph/commit.rb @@ -28,7 +28,7 @@ module Gitlab h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? h[:id] = sha h[:date] = date - h[:message] = escape_once(message) + h[:message] = message h[:login] = author.email h end diff --git a/lib/gitlab/graph/json_builder.rb b/lib/gitlab/graph/json_builder.rb index c2c3fa662a65271dd0faffffe7adf8ad554777ac..a59143633935c77be5d037290a4e042ab0136262 100644 --- a/lib/gitlab/graph/json_builder.rb +++ b/lib/gitlab/graph/json_builder.rb @@ -17,16 +17,15 @@ module Gitlab @commits = collect_commits @days = index_commits end - - def days_json - @days_json = @days.compact.map { |d| [d.day, d.strftime("%b")] }.to_json - end - - def commits_json - @commits_json = @commits.map(&:to_graph_hash).to_json + + def to_json(*args) + { + days: @days.compact.map { |d| [d.day, d.strftime("%b")] }, + commits: @commits.map(&:to_graph_hash) + }.to_json(*args) end - - protected + + protected # Get commits from repository # diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index cf9a4c4afa21e98c185f9823f9b151fae74e7f0c..389eef3395f7c5f1bed4e9d4ac51d2b29b1692e1 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -11,7 +11,12 @@ module Gitlab def self.read_latest path = Rails.root.join("log", file_name) self.build unless File.exist?(path) - logs = File.read(path).split("\n") + logs = `tail -n 2000 #{path}`.split("\n") + end + + def self.read_latest_for filename + path = Rails.root.join("log", filename) + logs = `tail -n 2000 #{path}`.split("\n") end def self.build diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index ee0ee05c3befe47c35b2991a98dd0b19891ead54..859184b6c3ad028acbbcd09629f79819807d7f25 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -26,15 +26,19 @@ module Gitlab # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" /> module Markdown REFERENCE_PATTERN = %r{ - (\W)? # Prefix (1) - ( # Reference (2) - @([\w\._]+) # User name (3) - |[#!$](\d+) # Issue/MR/Snippet ID (4) - |([\h]{6,40}) # Commit ID (5) + (?<prefix>\W)? # Prefix + ( # Reference + @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name + |\#(?<issue>\d+) # Issue ID + |!(?<merge_request>\d+) # MR ID + |\$(?<snippet>\d+) # Snippet ID + |(?<commit>[\h]{6,40}) # Commit ID ) - (\W)? # Suffix (6) + (?<suffix>\W)? # Suffix }x.freeze + TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze + EMOJI_PATTERN = %r{(:(\S+):)}.freeze attr_reader :html_options @@ -95,16 +99,16 @@ module Gitlab def parse_references(text) # parse reference links text.gsub!(REFERENCE_PATTERN) do |match| - prefix = $1 || '' - reference = $2 - identifier = $3 || $4 || $5 - suffix = $6 || '' + prefix = $~[:prefix] + suffix = $~[:suffix] + type = TYPES.select{|t| !$~[t].nil?}.first + identifier = $~[type] # Avoid HTML entities - if prefix.ends_with?('&') || suffix.starts_with?(';') + if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';' match - elsif ref_link = reference_link(reference, identifier) - prefix + ref_link + suffix + elsif ref_link = reference_link(type, identifier) + "#{prefix}#{ref_link}#{suffix}" else match end @@ -137,19 +141,12 @@ module Gitlab # identifier - Object identifier (Issue ID, SHA hash, etc.) # # Returns string rendered by the processing method - def reference_link(reference, identifier) - case reference - when /^@/ then reference_user(identifier) - when /^#/ then reference_issue(identifier) - when /^!/ then reference_merge_request(identifier) - when /^\$/ then reference_snippet(identifier) - when /^\h/ then reference_commit(identifier) - end + def reference_link(type, identifier) + send("reference_#{type}", identifier) end def reference_user(identifier) - if user = @project.users.where(name: identifier).first - member = @project.users_projects.where(user_id: user).first + if member = @project.users_projects.joins(:user).where(users: { username: identifier }).first link_to("@#{identifier}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member end end diff --git a/lib/gitlab/project_mover.rb b/lib/gitlab/project_mover.rb new file mode 100644 index 0000000000000000000000000000000000000000..def6e90001beae92b01e009ca987da4316fbea8c --- /dev/null +++ b/lib/gitlab/project_mover.rb @@ -0,0 +1,44 @@ +# ProjectMover class +# +# Used for moving project repositories from one subdir to another +module Gitlab + class ProjectMover + class ProjectMoveError < StandardError; end + + attr_reader :project, :old_dir, :new_dir + + def initialize(project, old_dir, new_dir) + @project = project + @old_dir = old_dir + @new_dir = new_dir + end + + def execute + # Create new dir if missing + new_dir_path = File.join(Gitlab.config.gitolite.repos_path, new_dir) + system("mkdir -m 770 #{new_dir_path}") unless File.exists?(new_dir_path) + + old_path = File.join(Gitlab.config.gitolite.repos_path, old_dir, "#{project.path}.git") + new_path = File.join(new_dir_path, "#{project.path}.git") + + if File.exists? new_path + raise ProjectMoveError.new("Destination #{new_path} already exists") + end + + if system("mv #{old_path} #{new_path}") + log_info "Project #{project.name} was moved from #{old_path} to #{new_path}" + true + else + message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}" + log_info "Error! #{message}" + raise ProjectMoveError.new(message) + end + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb new file mode 100644 index 0000000000000000000000000000000000000000..a3f38b1c36012bffdfdff544437017b8158edc4e --- /dev/null +++ b/lib/gitlab/regex.rb @@ -0,0 +1,19 @@ +module Gitlab + module Regex + extend self + + def username_regex + default_regex + end + + def path_regex + default_regex + end + + protected + + def default_regex + /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/ + end + end +end diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 28b6f538d0078f9df10cd699b849ea137f2da613..784b146b98ad9cff6f44e59e8c7e186183eb265a 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -18,7 +18,13 @@ module Gitlab end def create - `git clone #{project.url_to_repo} #{path}` + create_cmd = "git clone #{project.url_to_repo} #{path}" + if system(create_cmd) + true + else + Gitlab::GitLogger.error("Failed to create satellite for #{project.name_with_namespace}") + false + end end def exists? @@ -41,11 +47,11 @@ module Gitlab end def lock_file - Rails.root.join("tmp", "#{project.path}.lock") + Rails.root.join("tmp", "satellite_#{project.id}.lock") end def path - Rails.root.join("tmp", "repo_satellites", project.path) + Rails.root.join("tmp", "repo_satellites", project.path_with_namespace) end def repo diff --git a/lib/hooks/post-receive b/lib/hooks/post-receive index 4a3ce372eb8177a7f1f31980beac6c48ced888c0..ebd9e1a028a48385aac42a5ec858b33a3001767a 100755 --- a/lib/hooks/post-receive +++ b/lib/hooks/post-receive @@ -6,7 +6,6 @@ while read oldrev newrev ref do # For every branch or tag that was pushed, create a Resque job in redis. - pwd=`pwd` - reponame=`basename "$pwd" | sed s/\.git$//` - env -i redis-cli rpush "resque:gitlab:queue:post_receive" "{\"class\":\"PostReceive\",\"args\":[\"$reponame\",\"$oldrev\",\"$newrev\",\"$ref\",\"$GL_USER\"]}" > /dev/null 2>&1 + repo_path=`pwd` + env -i redis-cli rpush "resque:gitlab:queue:post_receive" "{\"class\":\"PostReceive\",\"args\":[\"$repo_path\",\"$oldrev\",\"$newrev\",\"$ref\",\"$GL_USER\"]}" > /dev/null 2>&1 done diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 48b4da9d3395246b772b14cdb09519521fd86a65..3a430e0bf9ecbb9cbf891297a5603cd678196461 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -11,12 +11,20 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML def block_code(code, language) options = { options: {encoding: 'utf-8'} } + options.merge!(lexer: language.downcase) if Pygments::Lexer.find(language) - if Pygments::Lexer.find(language) - Pygments.highlight(code, options.merge(lexer: language.downcase)) - else - Pygments.highlight(code, options) - end + # New lines are placed to fix an rendering issue + # with code wrapped inside <h1> tag for next case: + # + # # Title kinda h1 + # + # ruby code here + # + <<-HTML + + <div class="#{h.user_color_scheme_class}">#{Pygments.highlight(code, options)}</div> + + HTML end def postprocess(full_document) diff --git a/lib/tasks/bulk_add_permission.rake b/lib/tasks/bulk_add_permission.rake deleted file mode 100644 index bf08ace8e9c0f2875ef622acf8ace2c871a3bfdb..0000000000000000000000000000000000000000 --- a/lib/tasks/bulk_add_permission.rake +++ /dev/null @@ -1,20 +0,0 @@ -desc "Add all users to all projects (admin users are added as masters)" -task :add_users_to_project_teams => :environment do |t, args| - user_ids = User.where(:admin => false).pluck(:id) - admin_ids = User.where(:admin => true).pluck(:id) - - Project.find_each do |project| - puts "Importing #{user_ids.size} users into #{project.code}" - UsersProject.bulk_import(project, user_ids, UsersProject::DEVELOPER) - puts "Importing #{admin_ids.size} admins into #{project.code}" - UsersProject.bulk_import(project, admin_ids, UsersProject::MASTER) - end -end - -desc "Add user to as a developer to all projects" -task :add_user_to_project_teams, [:email] => :environment do |t, args| - user = User.find_by_email args.email - project_ids = Project.pluck(:id) - - UsersProject.user_bulk_import(user, project_ids, UsersProject::DEVELOPER) -end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 17a0e336bb5fdc233b5c19f0d7235336404c991b..44da6d671e0d1de27b02807b0705043f3ee6e65f 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -1,14 +1,14 @@ require 'active_record/fixtures' namespace :gitlab do - namespace :app do + namespace :backup do # Create backup of GitLab system desc "GITLAB | Create a backup of the GitLab system" - task :backup_create => :environment do - Rake::Task["gitlab:app:db_dump"].invoke - Rake::Task["gitlab:app:repo_dump"].invoke + task :create => :environment do + Rake::Task["gitlab:backup:db:create"].invoke + Rake::Task["gitlab:backup:repo:create"].invoke - Dir.chdir(Gitlab.config.backup_path) + Dir.chdir(Gitlab.config.backup.path) # saving additional informations s = {} @@ -17,7 +17,7 @@ namespace :gitlab do s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"") s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"") - File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file| + File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file| file << s.to_yaml.gsub(/^---\n/,'') end @@ -39,10 +39,10 @@ namespace :gitlab do # delete backups print "Deleting old backups... " - if Gitlab.config.backup_keep_time > 0 + if Gitlab.config.backup.keep_time > 0 file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i } file_list.sort.each do |timestamp| - if Time.at(timestamp) < (Time.now - Gitlab.config.backup_keep_time) + if Time.at(timestamp) < (Time.now - Gitlab.config.backup.keep_time) %x{rm #{timestamp}_gitlab_backup.tar} end end @@ -54,15 +54,15 @@ namespace :gitlab do # Restore backup of GitLab system desc "GITLAB | Restore a previously created backup" - task :backup_restore => :environment do - Dir.chdir(Gitlab.config.backup_path) + task :restore => :environment do + Dir.chdir(Gitlab.config.backup.path) # check for existing backups in the backup dir file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } puts "no backups found" if file_list.count == 0 if file_list.count > 1 && ENV["BACKUP"].nil? puts "Found more than one backup, please specify which one you want to restore:" - puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup" + puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup" exit 1; end @@ -82,7 +82,7 @@ namespace :gitlab do end settings = YAML.load_file("backup_information.yml") - ENV["VERSION"] = "#{settings["db_version"]}" if settings["db_version"].to_i > 0 + ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 # restoring mismatching backups can lead to unexpected problems if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/,"") @@ -93,8 +93,8 @@ namespace :gitlab do exit 1 end - Rake::Task["gitlab:app:db_restore"].invoke - Rake::Task["gitlab:app:repo_restore"].invoke + Rake::Task["gitlab:backup:db:restore"].invoke + Rake::Task["gitlab:backup:repo:restore"].invoke # cleanup: remove tmp files print "Deleting tmp directories..." @@ -110,82 +110,86 @@ namespace :gitlab do ################################# REPOSITORIES ################################# - task :repo_dump => :environment do - backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") - FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo) - puts "Dumping repositories:" - project = Project.all.map { |n| [n.path, n.path_to_repo] } - project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] - project.each do |project| - print "- Dumping repository #{project.first}... " - if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1") - puts "[DONE]".green - else - puts "[FAILED]".red + namespace :repo do + task :create => :environment do + backup_path_repo = File.join(Gitlab.config.backup.path, "repositories") + FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo) + puts "Dumping repositories:" + project = Project.all.map { |n| [n.path, n.path_to_repo] } + project << ["gitolite-admin.git", File.join(Gitlab.config.git_base_path, "gitolite-admin.git")] + project.each do |project| + print "- Dumping repository #{project.first}... " + if Kernel.system("cd #{project.second} > /dev/null 2>&1 && git bundle create #{backup_path_repo}/#{project.first}.bundle --all > /dev/null 2>&1") + puts "[DONE]".green + else + puts "[FAILED]".red + end end end - end - task :repo_restore => :environment do - backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") - puts "Restoring repositories:" - project = Project.all.map { |n| [n.path, n.path_to_repo] } - project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] - project.each do |project| - print "- Restoring repository #{project.first}... " - FileUtils.rm_rf(project.second) if File.dirname(project.second) # delete old stuff - if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") - permission_commands = [ - "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", - "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}" - ] - permission_commands.each { |command| Kernel.system(command) } - puts "[DONE]".green - else - puts "[FAILED]".red + task :restore => :environment do + backup_path_repo = File.join(Gitlab.config.backup.path, "repositories") + puts "Restoring repositories:" + project = Project.all.map { |n| [n.path, n.path_to_repo] } + project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] + project.each do |project| + print "- Restoring repository #{project.first}... " + FileUtils.rm_rf(project.second) if File.dirname(project.second) # delete old stuff + if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") + permission_commands = [ + "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", + "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}" + ] + permission_commands.each { |command| Kernel.system(command) } + puts "[DONE]".green + else + puts "[FAILED]".red + end end end end ###################################### DB ###################################### - task :db_dump => :environment do - backup_path_db = File.join(Gitlab.config.backup_path, "db") - FileUtils.mkdir_p(backup_path_db) unless Dir.exists?(backup_path_db) - - puts "Dumping database tables:" - ActiveRecord::Base.connection.tables.each do |tbl| - print "- Dumping table #{tbl}... " - count = 1 - File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file| - ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line| - line.delete_if{|k,v| v.blank?} - output = {tbl + '_' + count.to_s => line} - file << output.to_yaml.gsub(/^---\n/,'') + "\n" - count += 1 + namespace :db do + task :create => :environment do + backup_path_db = File.join(Gitlab.config.backup.path, "db") + FileUtils.mkdir_p(backup_path_db) unless Dir.exists?(backup_path_db) + + puts "Dumping database tables:" + ActiveRecord::Base.connection.tables.each do |tbl| + print "- Dumping table #{tbl}... " + count = 1 + File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file| + ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line| + line.delete_if{|k,v| v.blank?} + output = {tbl + '_' + count.to_s => line} + file << output.to_yaml.gsub(/^---\n/,'') + "\n" + count += 1 + end + puts "[DONE]".green end - puts "[DONE]".green end end - end - task :db_restore=> :environment do - backup_path_db = File.join(Gitlab.config.backup_path, "db") + task :restore=> :environment do + backup_path_db = File.join(Gitlab.config.backup.path, "db") - puts "Restoring database tables:" - Rake::Task["db:reset"].invoke + puts "Restoring database tables:" + Rake::Task["db:reset"].invoke - Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir| - fixture_file = File.basename(dir, ".*" ) - print "- Loading fixture #{fixture_file}..." - if File.size(dir) > 0 - ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file) - puts "[DONE]".green - else - puts "[SKIPPING]".yellow + Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir| + fixture_file = File.basename(dir, ".*" ) + print "- Loading fixture #{fixture_file}..." + if File.size(dir) > 0 + ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file) + puts "[DONE]".green + else + puts "[SKIPPING]".yellow + end end end end - end # namespace end: app + end # namespace end: backup end # namespace end: gitlab diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake new file mode 100644 index 0000000000000000000000000000000000000000..36c51d060bc735a12bb203a1f189b943cd13d70c --- /dev/null +++ b/lib/tasks/gitlab/bulk_add_permission.rake @@ -0,0 +1,24 @@ +namespace :gitlab do + namespace :import do + desc "GITLAB | Add all users to all projects (admin users are added as masters)" + task :all_users_to_all_projects => :environment do |t, args| + user_ids = User.where(:admin => false).pluck(:id) + admin_ids = User.where(:admin => true).pluck(:id) + + Project.find_each do |project| + puts "Importing #{user_ids.size} users into #{project.code}" + UsersProject.bulk_import(project, user_ids, UsersProject::DEVELOPER) + puts "Importing #{admin_ids.size} admins into #{project.code}" + UsersProject.bulk_import(project, admin_ids, UsersProject::MASTER) + end + end + + desc "GITLAB | Add a specific user to all projects (as a developer)" + task :user_to_projects, [:email] => :environment do |t, args| + user = User.find_by_email args.email + project_ids = Project.pluck(:id) + + UsersProject.user_bulk_import(user, project_ids, UsersProject::DEVELOPER) + end + end +end \ No newline at end of file diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake new file mode 100644 index 0000000000000000000000000000000000000000..baa706d2beef05ff8d9c668a32900d16048be81d --- /dev/null +++ b/lib/tasks/gitlab/check.rake @@ -0,0 +1,968 @@ +namespace :gitlab do + desc "GITLAB | Check the configuration of GitLab and its environment" + task check: %w{gitlab:env:check + gitlab:gitolite:check + gitlab:resque:check + gitlab:app:check} + + + + namespace :app do + desc "GITLAB | Check the configuration of the GitLab Rails app" + task check: :environment do + warn_user_is_not_gitlab + start_checking "GitLab" + + check_database_config_exists + check_database_is_not_sqlite + check_migrations_are_up + check_gitlab_config_exists + check_gitlab_config_not_outdated + check_log_writable + check_tmp_writable + check_init_script_exists + check_init_script_up_to_date + check_satellites_exist + + finished_checking "GitLab" + end + + + # Checks + ######################## + + def check_database_config_exists + print "Database config exists? ... " + + database_config_file = Rails.root.join("config", "database.yml") + + if File.exists?(database_config_file) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Copy config/database.yml.<your db> to config/database.yml", + "Check that the information in config/database.yml is correct" + ) + for_more_information( + see_database_guide, + "http://guides.rubyonrails.org/getting_started.html#configuring-a-database" + ) + check_failed + end + end + + def check_database_is_not_sqlite + print "Database is not SQLite ... " + + database_config_file = Rails.root.join("config", "database.yml") + + unless File.read(database_config_file) =~ /sqlite/ + puts "yes".green + else + puts "no".red + for_more_information( + "https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL", + see_database_guide + ) + check_failed + end + end + + def check_gitlab_config_exists + print "GitLab config exists? ... " + + gitlab_config_file = Rails.root.join("config", "gitlab.yml") + + if File.exists?(gitlab_config_file) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Copy config/gitlab.yml.example to config/gitlab.yml", + "Update config/gitlab.yml to match your setup" + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + check_failed + end + end + + def check_gitlab_config_not_outdated + print "GitLab config outdated? ... " + + gitlab_config_file = Rails.root.join("config", "gitlab.yml") + unless File.exists?(gitlab_config_file) + puts "can't check because of previous errors".magenta + end + + # omniauth or ldap could have been deleted from the file + unless Gitlab.config.pre_40_config + puts "no".green + else + puts "yes".red + try_fixing_it( + "Backup your config/gitlab.yml", + "Copy config/gitlab.yml.example to config/gitlab.yml", + "Update config/gitlab.yml to match your setup" + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + check_failed + end + end + + def check_init_script_exists + print "Init script exists? ... " + + script_path = "/etc/init.d/gitlab" + + if File.exists?(script_path) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Install the init script" + ) + for_more_information( + see_installation_guide_section "Install Init Script" + ) + check_failed + end + end + + def check_init_script_up_to_date + print "Init script up-to-date? ... " + + script_path = "/etc/init.d/gitlab" + unless File.exists?(script_path) + puts "can't check because of previous errors".magenta + return + end + + recipe_content = `curl https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab 2>/dev/null` + script_content = File.read(script_path) + + if recipe_content == script_content + puts "yes".green + else + puts "no".red + try_fixing_it( + "Redownload the init script" + ) + for_more_information( + see_installation_guide_section "Install Init Script" + ) + check_failed + end + end + + def check_migrations_are_up + print "All migrations up? ... " + + migration_status = `bundle exec rake db:migrate:status` + + unless migration_status =~ /down\s+\d{14}/ + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo -u gitlab -H bundle exec rake db:migrate" + ) + check_failed + end + end + + def check_satellites_exist + print "Projects have satellites? ... " + + unless Project.count > 0 + puts "can't check, you have no projects".magenta + return + end + puts "" + + Project.find_each(batch_size: 100) do |project| + print "#{project.name_with_namespace.yellow} ... " + + if project.satellite.exists? + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo -u gitlab -H bundle exec rake gitlab:satellites:create", + "If necessary, remove the tmp/repo_satellites directory ...", + "... and rerun the above command" + ) + for_more_information( + "doc/raketasks/maintenance.md " + ) + check_failed + end + end + end + + def check_log_writable + print "Log directory writable? ... " + + log_path = Rails.root.join("log") + + if File.writable?(log_path) + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo chown -R gitlab #{log_path}", + "sudo chmod -R rwX #{log_path}" + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + check_failed + end + end + + def check_tmp_writable + print "Tmp directory writable? ... " + + tmp_path = Rails.root.join("tmp") + + if File.writable?(tmp_path) + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo chown -R gitlab #{tmp_path}", + "sudo chmod -R rwX #{tmp_path}" + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + check_failed + end + end + end + + + + namespace :env do + desc "GITLAB | Check the configuration of the environment" + task check: :environment do + warn_user_is_not_gitlab + start_checking "Environment" + + check_gitlab_in_git_group + check_issue_1056_shell_profile_error + check_gitlab_git_config + check_python2_exists + check_python2_version + + finished_checking "Environment" + end + + + # Checks + ######################## + + def check_gitlab_git_config + print "Git configured for gitlab user? ... " + + options = { + "user.name" => "GitLab", + "user.email" => Gitlab.config.gitlab.email_from + } + correct_options = options.map do |name, value| + run("git config --global --get #{name}").try(:squish) == value + end + + if correct_options.all? + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo -u gitlab -H git config --global user.name \"#{options["user.name"]}\"", + "sudo -u gitlab -H git config --global user.email \"#{options["user.email"]}\"" + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + check_failed + end + end + + def check_gitlab_in_git_group + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + print "gitlab user is in #{gitolite_ssh_user} group? ... " + + if run_and_match("id -rnG", /\Wgit\W/) + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo usermod -a -G #{gitolite_ssh_user} gitlab" + ) + for_more_information( + see_installation_guide_section "System Users" + ) + check_failed + end + end + + # see https://github.com/gitlabhq/gitlabhq/issues/1059 + def check_issue_1056_shell_profile_error + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + print "Has no \"-e\" in ~#{gitolite_ssh_user}/.profile ... " + + profile_file = File.expand_path("~#{Gitlab.config.gitolite.ssh_user}/.profile") + + unless File.read(profile_file) =~ /^-e PATH/ + puts "yes".green + else + puts "no".red + try_fixing_it( + "Open #{profile_file}", + "Find the line starting with \"-e PATH\"", + "Remove \"-e \" so the line starts with PATH" + ) + for_more_information( + see_installation_guide_section("Gitolite"), + "https://github.com/gitlabhq/gitlabhq/issues/1059" + ) + check_failed + end + end + + def check_python2_exists + print "Has python2? ... " + + # Python prints its version to STDERR + # so we can't just use run("python2 --version") + if run_and_match("which python2", /python2$/) + puts "yes".green + else + puts "no".red + try_fixing_it( + "Make sure you have Python 2.5+ installed", + "Link it to python2" + ) + for_more_information( + see_installation_guide_section "Packages / Dependencies" + ) + check_failed + end + end + + def check_python2_version + print "python2 is supported version? ... " + + # Python prints its version to STDERR + # so we can't just use run("python2 --version") + + unless run_and_match("which python2", /python2$/) + puts "can't check because of previous errors".magenta + return + end + + if `python2 --version 2>&1` =~ /2\.[567]\.\d/ + puts "yes".green + else + puts "no".red + try_fixing_it( + "Make sure you have Python 2.5+ installed", + "Link it to python2" + ) + for_more_information( + see_installation_guide_section "Packages / Dependencies" + ) + check_failed + end + end + end + + + + namespace :gitolite do + desc "GITLAB | Check the configuration of Gitolite" + task check: :environment do + warn_user_is_not_gitlab + start_checking "Gitolite" + + check_gitolite_is_up_to_date + check_gitoliterc_repo_umask + check_gitoliterc_git_config_keys + check_dot_gitolite_exists + check_dot_gitolite_user_and_group + check_dot_gitolite_permissions + check_repo_base_exists + check_repo_base_user_and_group + check_repo_base_permissions + check_can_clone_gitolite_admin + check_can_commit_to_gitolite_admin + check_post_receive_hook_exists + check_post_receive_hook_is_up_to_date + check_repos_post_receive_hooks_is_link + check_repos_git_config + + finished_checking "Gitolite" + end + + + # Checks + ######################## + + def check_can_clone_gitolite_admin + print "Can clone gitolite-admin? ... " + + test_path = "/tmp/gitlab_gitolite_admin_test" + FileUtils.rm_rf(test_path) + `git clone -q #{Gitlab.config.gitolite.admin_uri} #{test_path}` + raise unless $?.success? + + puts "yes".green + rescue + puts "no".red + try_fixing_it( + "Make sure the \"admin_uri\" is set correctly in config/gitlab.yml", + "Try cloning it yourself with:", + " git clone -q #{Gitlab.config.gitolite.admin_uri} /tmp/gitolite-admin", + "Make sure Gitolite is installed correctly." + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + + # assumes #check_can_clone_gitolite_admin has been run before + def check_can_commit_to_gitolite_admin + print "Can commit to gitolite-admin? ... " + + test_path = "/tmp/gitlab_gitolite_admin_test" + unless File.exists?(test_path) + puts "can't check because of previous errors".magenta + return + end + + Dir.chdir(test_path) do + `touch foo && git add foo && git commit -qm foo` + raise unless $?.success? + end + + puts "yes".green + rescue + puts "no".red + try_fixing_it( + "Try committing to it yourself with:", + " git clone -q #{Gitlab.config.gitolite.admin_uri} /tmp/gitolite-admin", + " touch foo", + " git add foo", + " git commit -m \"foo\"", + "Make sure Gitolite is installed correctly." + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + ensure + FileUtils.rm_rf("/tmp/gitolite_gitlab_test") + end + + def check_dot_gitolite_exists + print "Config directory exists? ... " + + gitolite_config_path = File.expand_path("~#{Gitlab.config.gitolite.ssh_user}/.gitolite") + + if File.directory?(gitolite_config_path) + puts "yes".green + else + puts "no".red + puts "#{gitolite_config_path} is missing".red + try_fixing_it( + "This should have been created when setting up Gitolite.", + "Make sure Gitolite is installed correctly." + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_dot_gitolite_permissions + print "Config directory access is drwxr-x---? ... " + + gitolite_config_path = File.expand_path("~#{Gitlab.config.gitolite.ssh_user}/.gitolite") + unless File.exists?(gitolite_config_path) + puts "can't check because of previous errors".magenta + return + end + + if `stat --printf %a #{gitolite_config_path}` == "750" + puts "yes".green + else + puts "no".red + puts "#{gitolite_config_path} is not writable".red + try_fixing_it( + "sudo chmod 750 #{gitolite_config_path}" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_dot_gitolite_user_and_group + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + print "Config directory owned by #{gitolite_ssh_user}:#{gitolite_ssh_user} ... " + + gitolite_config_path = File.expand_path("~#{gitolite_ssh_user}/.gitolite") + unless File.exists?(gitolite_config_path) + puts "can't check because of previous errors".magenta + return + end + + if `stat --printf %U #{gitolite_config_path}` == gitolite_ssh_user && # user + `stat --printf %G #{gitolite_config_path}` == gitolite_ssh_user #group + puts "yes".green + else + puts "no".red + puts "#{gitolite_config_path} is not owned by #{gitolite_ssh_user}".red + try_fixing_it( + "sudo chown -R #{gitolite_ssh_user}:#{gitolite_ssh_user} #{gitolite_config_path}" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_gitolite_is_up_to_date + print "Using recommended version ... " + if gitolite_version.try(:start_with?, "v3.04") + puts "yes".green + else + puts "no".red + try_fixing_it( + "We strongly recommend using the version pointed out in the installation guide." + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + # this is not a "hard" failure + end + end + + def check_gitoliterc_git_config_keys + gitoliterc_path = File.join(gitolite_home, ".gitolite.rc") + + print "Allow all Git config keys in .gitolite.rc ... " + option_name = if has_gitolite3? + # see https://github.com/sitaramc/gitolite/blob/v3.04/src/lib/Gitolite/Rc.pm#L329 + "GIT_CONFIG_KEYS" + else + # assume older version + # see https://github.com/sitaramc/gitolite/blob/v2.3/conf/example.gitolite.rc#L49 + "$GL_GITCONFIG_KEYS" + end + option_value = ".*" + if open(gitoliterc_path).grep(/#{option_name}\s*=[>]?\s*["']#{option_value}["']/).any? + puts "yes".green + else + puts "no".red + try_fixing_it( + "Open #{gitoliterc_path}", + "Find the \"#{option_name}\" option", + "Change its value to \".*\"" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_gitoliterc_repo_umask + gitoliterc_path = File.join(gitolite_home, ".gitolite.rc") + + print "Repo umask is 0007 in .gitolite.rc? ... " + option_name = if has_gitolite3? + # see https://github.com/sitaramc/gitolite/blob/v3.04/src/lib/Gitolite/Rc.pm#L328 + "UMASK" + else + # assume older version + # see https://github.com/sitaramc/gitolite/blob/v2.3/conf/example.gitolite.rc#L32 + "$REPO_UMASK" + end + option_value = "0007" + if open(gitoliterc_path).grep(/#{option_name}\s*=[>]?\s*#{option_value}/).any? + puts "yes".green + else + puts "no".red + try_fixing_it( + "Open #{gitoliterc_path}", + "Find the \"#{option_name}\" option", + "Change its value to \"0007\"" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_post_receive_hook_exists + print "post-receive hook exists? ... " + + hook_file = "post-receive" + gitolite_hooks_path = File.join(Gitlab.config.gitolite.hooks_path, "common") + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + + gitlab_hook_file = Rails.root.join.join("lib", "hooks", hook_file) + + if File.exists?(gitolite_hook_file) + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo -u #{gitolite_ssh_user} cp #{gitlab_hook_file} #{gitolite_hook_file}" + ) + for_more_information( + see_installation_guide_section "Setup GitLab Hooks" + ) + check_failed + end + end + + def check_post_receive_hook_is_up_to_date + print "post-receive hook up-to-date? ... " + + hook_file = "post-receive" + gitolite_hooks_path = File.join(Gitlab.config.gitolite.hooks_path, "common") + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) + gitolite_hook_content = File.read(gitolite_hook_file) + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + + unless File.exists?(gitolite_hook_file) + puts "can't check because of previous errors".magenta + return + end + + gitlab_hook_file = Rails.root.join.join("lib", "hooks", hook_file) + gitlab_hook_content = File.read(gitlab_hook_file) + + if gitolite_hook_content == gitlab_hook_content + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo -u #{gitolite_ssh_user} cp #{gitlab_hook_file} #{gitolite_hook_file}" + ) + for_more_information( + see_installation_guide_section "Setup GitLab Hooks" + ) + check_failed + end + end + + def check_repo_base_exists + print "Repo base directory exists? ... " + + repo_base_path = Gitlab.config.gitolite.repos_path + + if File.exists?(repo_base_path) + puts "yes".green + else + puts "no".red + puts "#{repo_base_path} is missing".red + try_fixing_it( + "This should have been created when setting up Gitolite.", + "Make sure it's set correctly in config/gitlab.yml", + "Make sure Gitolite is installed correctly." + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_repo_base_permissions + print "Repo base access is drwsrws---? ... " + + repo_base_path = Gitlab.config.gitolite.repos_path + unless File.exists?(repo_base_path) + puts "can't check because of previous errors".magenta + return + end + + if `stat --printf %a #{repo_base_path}` == "6770" + puts "yes".green + else + puts "no".red + puts "#{repo_base_path} is not writable".red + try_fixing_it( + "sudo chmod -R ug+rwXs,o-rwx #{repo_base_path}" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_repo_base_user_and_group + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + print "Repo base owned by #{gitolite_ssh_user}:#{gitolite_ssh_user}? ... " + + repo_base_path = Gitlab.config.gitolite.repos_path + unless File.exists?(repo_base_path) + puts "can't check because of previous errors".magenta + return + end + + if `stat --printf %U #{repo_base_path}` == gitolite_ssh_user && # user + `stat --printf %G #{repo_base_path}` == gitolite_ssh_user #group + puts "yes".green + else + puts "no".red + puts "#{repo_base_path} is not owned by #{gitolite_ssh_user}".red + try_fixing_it( + "sudo chown -R #{gitolite_ssh_user}:#{gitolite_ssh_user} #{repo_base_path}" + ) + for_more_information( + see_installation_guide_section "Gitolite" + ) + check_failed + end + end + + def check_repos_git_config + print "Git config in repos: ... " + + unless Project.count > 0 + puts "can't check, you have no projects".magenta + return + end + puts "" + + options = { + "core.sharedRepository" => "0660", + } + + Project.find_each(batch_size: 100) do |project| + print "#{project.name_with_namespace.yellow} ... " + + correct_options = options.map do |name, value| + run("git --git-dir=\"#{project.path_to_repo}\" config --get #{name}").try(:chomp) == value + end + + if correct_options.all? + puts "ok".green + else + puts "wrong or missing".red + try_fixing_it( + "sudo -u gitlab -H bundle exec rake gitlab:gitolite:update_repos" + ) + for_more_information( + "doc/raketasks/maintenance.md" + ) + check_failed + end + end + end + + def check_repos_post_receive_hooks_is_link + print "post-receive hooks in repos are links: ... " + + hook_file = "post-receive" + gitolite_hooks_path = File.join(Gitlab.config.gitolite.hooks_path, "common") + gitolite_hook_file = File.join(gitolite_hooks_path, hook_file) + gitolite_ssh_user = Gitlab.config.gitolite.ssh_user + + unless File.exists?(gitolite_hook_file) + puts "can't check because of previous errors".magenta + return + end + + unless Project.count > 0 + puts "can't check, you have no projects".magenta + return + end + puts "" + + Project.find_each(batch_size: 100) do |project| + print "#{project.name_with_namespace.yellow} ... " + project_hook_file = File.join(project.path_to_repo, "hooks", hook_file) + + unless File.exists?(project_hook_file) + puts "missing".red + try_fixing_it( + "sudo -u #{gitolite_ssh_user} ln -sf #{gitolite_hook_file} #{project_hook_file}" + ) + for_more_information( + "lib/support/rewrite-hooks.sh" + ) + check_failed + next + end + + if run_and_match("stat --format %N #{project_hook_file}", /#{hook_file}.+->.+#{gitolite_hook_file}/) + puts "ok".green + else + puts "not a link to Gitolite's hook".red + try_fixing_it( + "sudo -u #{gitolite_ssh_user} ln -sf #{gitolite_hook_file} #{project_hook_file}" + ) + for_more_information( + "lib/support/rewrite-hooks.sh" + ) + check_failed + end + end + end + + + # Helper methods + ######################## + + def gitolite_home + File.expand_path("~#{Gitlab.config.gitolite.ssh_user}") + end + + def gitolite_version + gitolite_version_file = "#{gitolite_home}/gitolite/src/VERSION" + if File.readable?(gitolite_version_file) + File.read(gitolite_version_file) + end + end + + def has_gitolite3? + gitolite_version.try(:start_with?, "v3.") + end + end + + + + namespace :resque do + desc "GITLAB | Check the configuration of Resque" + task check: :environment do + warn_user_is_not_gitlab + start_checking "Resque" + + check_resque_running + + finished_checking "Resque" + end + + + # Checks + ######################## + + def check_resque_running + print "Running? ... " + + if run_and_match("ps aux | grep -i resque", /resque-[\d\.]+:.+$/) + puts "yes".green + else + puts "no".red + try_fixing_it( + "sudo service gitlab restart", + "or", + "sudo /etc/init.d/gitlab restart" + ) + for_more_information( + see_installation_guide_section("Install Init Script"), + "see log/resque.log for possible errors" + ) + check_failed + end + end + end + + + # Helper methods + ########################## + + def check_failed + puts " Please #{"fix the error above"} and rerun the checks.".red + end + + def for_more_information(*sources) + sources = sources.shift if sources.first.is_a?(Array) + + puts " For more information see:".blue + sources.each do |source| + puts " #{source}" + end + end + + def finished_checking(component) + puts "" + puts "Checking #{component.yellow} ... #{"Finished".green}" + puts "" + end + + # Runs the given command + # + # Returns nil if the command was not found + # Returns the output of the command otherwise + # + # see also #run_and_match + def run(command) + unless `#{command} 2>/dev/null`.blank? + `#{command}` + end + end + + # Runs the given command and matches the output agains the given pattern + # + # Returns nil if nothing matched + # Retunrs the MatchData if the pattern matched + # + # see also #run + # see also String#match + def run_and_match(command, pattern) + run(command).try(:match, pattern) + end + + def see_database_guide + "doc/install/databases.md" + end + + def see_installation_guide_section(section) + "doc/install/installation.md in section \"#{section}\"" + end + + def start_checking(component) + puts "Checking #{component.yellow} ..." + puts "" + end + + def try_fixing_it(*steps) + steps = steps.shift if steps.first.is_a?(Array) + + puts " Try fixing it:".blue + steps.each do |step| + puts " #{step}" + end + end + + def warn_user_is_not_gitlab + unless @warned_user_not_gitlab + current_user = run("whoami").chomp + unless current_user == "gitlab" + puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}" + puts " You are running as user #{current_user.magenta}, we hope you know what you are doing." + puts " Some tests may pass\/fail for the wrong reason." + puts " For meaningful results you should run this as user #{"gitlab".magenta}." + puts "" + end + @warned_user_not_gitlab = true + end + end +end diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake index 13b4bab6edc43b47964a4e7b6a6829b099f2698d..ed3d6368a998d1aae98f67ecd75983838adb578b 100644 --- a/lib/tasks/gitlab/enable_automerge.rake +++ b/lib/tasks/gitlab/enable_automerge.rake @@ -1,17 +1,20 @@ namespace :gitlab do - namespace :app do - desc "GITLAB | Enable auto merge" - task :enable_automerge => :environment do - Gitlab::Gitolite.new.enable_automerge + desc "GITLAB | Enable auto merge" + task :enable_automerge => :environment do + Gitlab::Gitolite.new.enable_automerge - Project.find_each do |project| - if project.repo_exists? && !project.satellite.exists? - puts "Creating satellite for #{project.name}...".green - project.satellite.create - end + Project.find_each do |project| + if project.repo_exists? && !project.satellite.exists? + puts "Creating satellite for #{project.name}...".green + project.satellite.create end - - puts "Done!".green end + + puts "Done!".green + end + + namespace :satellites do + desc "GITLAB | Create satellite repos" + task create: 'gitlab:enable_automerge' end end diff --git a/lib/tasks/gitlab/enable_namespaces.rake b/lib/tasks/gitlab/enable_namespaces.rake new file mode 100644 index 0000000000000000000000000000000000000000..1be9ba6469d29860c9a3f62df7002d25136fa0e1 --- /dev/null +++ b/lib/tasks/gitlab/enable_namespaces.rake @@ -0,0 +1,67 @@ +namespace :gitlab do + desc "GITLAB | Enable usernames and namespaces for user projects" + task enable_namespaces: :environment do + print "\nUsernames for users:".yellow + + User.find_each(batch_size: 500) do |user| + next if user.namespace + + User.transaction do + username = user.email.match(/^[^@]*/)[0] + if user.update_attributes!(username: username) + print '.'.green + else + print 'F'.red + end + end + end + + print "\n\nDirs for groups:".yellow + + Group.find_each(batch_size: 500) do |group| + if group.ensure_dir_exist + print '.'.green + else + print 'F'.red + end + end + + print "\n\nMove projects from groups under groups dirs:".yellow + git_path = Gitlab.config.gitolite.repos_path + + Project.where('namespace_id IS NOT NULL').find_each(batch_size: 500) do |project| + next unless project.group + + group = project.group + + puts "\n" + print " * #{project.name}: " + + new_path = File.join(git_path, project.path_with_namespace + '.git') + + if File.exists?(new_path) + print "ok. already at #{new_path}".cyan + next + end + + old_path = File.join(git_path, project.path + '.git') + + unless File.exists?(old_path) + print "missing. not found at #{old_path}".red + next + end + + begin + Gitlab::ProjectMover.new(project, '', group.path).execute + print "ok. Moved to #{new_path}".green + rescue + print "Failed moving to #{new_path}".red + end + end + + print "\n\nRebuild gitolite:".yellow + gitolite = Gitlab::Gitolite.new + gitolite.update_repositories(Project.where('namespace_id IS NOT NULL')) + puts "\n" + end +end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 09f0dc9e45983be29c786533f82e34722a1e845d..4bf9110508ed41a12ba25d6517364a45daa5f681 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -12,18 +12,26 @@ namespace :gitlab do desc "GITLAB | Import bare repositories from git_host -> base_path into GitLab project instance" task :repos => :environment do - git_base_path = Gitlab.config.git_base_path + git_base_path = Gitlab.config.gitolite.repos_path repos_to_import = Dir.glob(git_base_path + '/*') + namespaces = Namespace.pluck(:path) + repos_to_import.each do |repo_path| repo_name = File.basename repo_path + # Skip if group or user + next if namespaces.include?(repo_name) + + # skip if not git repo + next unless repo_name =~ /.git$/ + # skip gitolite admin next if repo_name == 'gitolite-admin.git' path = repo_name.sub(/\.git$/, '') - project = Project.find_by_path(path) + project = Project.find_with_namespace(path) puts "Processing #{repo_name}".yellow @@ -34,8 +42,6 @@ namespace :gitlab do project_params = { :name => path, - :code => path, - :path => path, } project = Project.create_by_user(project_params, user) diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake new file mode 100644 index 0000000000000000000000000000000000000000..85458fe2c43fdd53888671878d2a7afdb64c15f5 --- /dev/null +++ b/lib/tasks/gitlab/info.rake @@ -0,0 +1,110 @@ +namespace :gitlab do + namespace :env do + desc "GITLAB | Show information about GitLab and its environment" + task info: :environment do + + # check which OS is running + os_name = run("lsb_release -irs") + os_name ||= if File.readable?('/etc/system-release') + File.read('/etc/system-release') + end + os_name ||= if File.readable?('/etc/debian_version') + debian_version = File.read('/etc/debian_version') + "Debian #{debian_version}" + end + os_name.squish! + + # check if there is an RVM environment + rvm_version = run_and_match("rvm --version", /[\d\.]+/).try(:to_s) + # check Ruby version + ruby_version = run_and_match("ruby --version", /[\d\.p]+/).try(:to_s) + # check Gem version + gem_version = run("gem --version") + # check Bundler version + bunder_version = run_and_match("bundle --version", /[\d\.]+/).try(:to_s) + # check Bundler version + rake_version = run_and_match("rake --version", /[\d\.]+/).try(:to_s) + + puts "" + puts "System information".yellow + puts "System:\t\t#{os_name || "unknown".red}" + puts "Current User:\t#{`whoami`}" + puts "Using RVM:\t#{rvm_version.present? ? "yes".green : "no"}" + puts "RVM Version:\t#{rvm_version}" if rvm_version.present? + puts "Ruby Version:\t#{ruby_version || "unknown".red}" + puts "Gem Version:\t#{gem_version || "unknown".red}" + puts "Bundler Version:#{bunder_version || "unknown".red}" + puts "Rake Version:\t#{rake_version || "unknown".red}" + + + # check database adapter + database_adapter = ActiveRecord::Base.connection.adapter_name.downcase + + project = Project.new(path: "some-project") + project.path = "some-project" + # construct clone URLs + http_clone_url = project.http_url_to_repo + ssh_clone_url = project.ssh_url_to_repo + + omniauth_providers = Gitlab.config.omniauth.providers + omniauth_providers.map! { |provider| provider['name'] } + + puts "" + puts "GitLab information".yellow + puts "Version:\t#{Gitlab::Version}" + puts "Revision:\t#{Gitlab::Revision}" + puts "Directory:\t#{Rails.root}" + puts "DB Adapter:\t#{database_adapter}" + puts "URL:\t\t#{Gitlab.config.gitlab.url}" + puts "HTTP Clone URL:\t#{http_clone_url}" + puts "SSH Clone URL:\t#{ssh_clone_url}" + puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".green : "no"}" + puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".green : "no"}" + puts "Omniauth Providers: #{omniauth_providers.map(&:magenta).join(', ')}" if Gitlab.config.omniauth.enabled + + + + # check Gitolite version + gitolite_version_file = "#{Gitlab.config.gitolite.repos_path}/../gitolite/src/VERSION" + if File.exists?(gitolite_version_file) && File.readable?(gitolite_version_file) + gitolite_version = File.read(gitolite_version_file) + end + + puts "" + puts "Gitolite information".yellow + puts "Version:\t#{gitolite_version || "unknown".red}" + puts "Admin URI:\t#{Gitlab.config.gitolite.admin_uri}" + puts "Admin Key:\t#{Gitlab.config.gitolite.admin_key}" + puts "Repositories:\t#{Gitlab.config.gitolite.repos_path}" + puts "Hooks:\t\t#{Gitlab.config.gitolite.hooks_path}" + puts "Git:\t\t#{Gitlab.config.git.bin_path}" + + end + + + # Helper methods + + # Runs the given command and matches the output agains the given pattern + # + # Returns nil if nothing matched + # Retunrs the MatchData if the pattern matched + # + # see also #run + # see also String#match + def run_and_match(command, regexp) + run(command).try(:match, regexp) + end + + # Runs the given command + # + # Returns nil if the command was not found + # Returns the output of the command otherwise + # + # see also #run_and_match + def run(command) + unless `#{command} 2>/dev/null`.blank? + `#{command}` + end + end + end +end diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake index 08f35c7e3f04d0d0f57a5495289dbc7d70fb0e90..572a22aa1f6028f1add82ee6b230cc3dd1fadc85 100644 --- a/lib/tasks/gitlab/setup.rake +++ b/lib/tasks/gitlab/setup.rake @@ -4,7 +4,7 @@ namespace :gitlab do task :setup => [ 'db:setup', 'db:seed_fu', - 'gitlab:app:enable_automerge' + 'gitlab:enable_automerge' ] end end diff --git a/lib/tasks/gitlab/status.rake b/lib/tasks/gitlab/status.rake deleted file mode 100644 index cbc77abb97a958577c959d72a9baad16cf20f166..0000000000000000000000000000000000000000 --- a/lib/tasks/gitlab/status.rake +++ /dev/null @@ -1,113 +0,0 @@ -namespace :gitlab do - namespace :app do - desc "GITLAB | Check GitLab installation status" - task :status => :environment do - puts "\nStarting diagnostics".yellow - git_base_path = Gitlab.config.git_base_path - - print "config/database.yml............" - if File.exists?(Rails.root.join "config", "database.yml") - puts "exists".green - else - puts "missing".red - return - end - - print "config/gitlab.yml............" - if File.exists?(Rails.root.join "config", "gitlab.yml") - puts "exists".green - else - puts "missing".red - return - end - - print "#{git_base_path}............" - if File.exists?(git_base_path) - puts "exists".green - else - puts "missing".red - return - end - - print "#{git_base_path} is writable?............" - if File.stat(git_base_path).writable? - puts "YES".green - else - puts "NO".red - return - end - - FileUtils.rm_rf("/tmp/gitolite_gitlab_test") - begin - `git clone -q #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite_gitlab_test` - raise unless $?.success? - print "Can clone gitolite-admin?............" - puts "YES".green - rescue - print "Can clone gitolite-admin?............" - puts "NO".red - return - end - - begin - Dir.chdir("/tmp/gitolite_gitlab_test") do - `touch blah && git add blah && git commit -qm blah -- blah` - raise unless $?.success? - end - print "Can git commit?............" - puts "YES".green - rescue - print "Can git commit?............" - puts "NO".red - return - ensure - FileUtils.rm_rf("/tmp/gitolite_gitlab_test") - end - - print "UMASK for .gitolite.rc is 0007? ............" - if open(File.absolute_path("#{git_base_path}/../.gitolite.rc")).grep(/UMASK([ \t]*)=([ \t>]*)0007/).any? - puts "YES".green - else - puts "NO".red - return - end - - gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") - gitlab_hook_files = ['post-receive'] - gitlab_hook_files.each do |file_name| - dest = File.join(gitolite_hooks_path, file_name) - print "#{dest} exists? ............" - if File.exists?(dest) - puts "YES".green - else - puts "NO".red - return - end - end - - if Project.count > 0 - puts "\nValidating projects repositories:".yellow - Project.find_each(:batch_size => 100) do |project| - print "* #{project.name}....." - hook_file = File.join(project.path_to_repo, 'hooks', 'post-receive') - - unless File.exists?(hook_file) - puts "post-receive file missing".red - next - end - - original_content = File.read(Rails.root.join('lib', 'hooks', 'post-receive')) - new_content = File.read(hook_file) - - if original_content == new_content - puts "post-receive file ok".green - else - puts "post-receive file content does not match".red - end - end - end - - puts "\nFinished".blue - end - end -end diff --git a/lib/tasks/resque.rake b/lib/tasks/resque.rake index 9b30bb0a292f493109fc0702c14b74e744f736df..0825324a424812fcc902cbf9a4326856ed85332c 100644 --- a/lib/tasks/resque.rake +++ b/lib/tasks/resque.rake @@ -1 +1,10 @@ require 'resque/tasks' + +task "resque:setup" => :environment do + Resque.after_fork do + Resque.redis.client.reconnect + end +end + +desc "Alias for resque:work (To run workers on Heroku)" +task "jobs:work" => "resque:work" diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake index 13e32135c66d10a709da613c0bc48b16635f37c7..e04bfbaf1c074245338854f887336e5e4ef9b2fc 100644 --- a/lib/tasks/travis.rake +++ b/lib/tasks/travis.rake @@ -1,5 +1,5 @@ task :travis do - ["spinach", "rspec spec"].each do |cmd| + ["rake spinach", "rake spec"].each do |cmd| puts "Starting to run #{cmd}..." system("export DISPLAY=:99.0 && bundle exec #{cmd}") raise "#{cmd} failed!" unless $?.exitstatus == 0 diff --git a/public/404.html b/public/404.html index 3e56e52cc183e5641478cd6e1d52de8243fcf7e3..867f193a98f573e65a69b336c8205ea392c84c0e 100644 --- a/public/404.html +++ b/public/404.html @@ -7,9 +7,8 @@ <body> <h1>404</h1> - <div> - <h2>The page you were looking for doesn't exist.</h2> - <p>You may have mistyped the address or the page may have moved.</p> - </div> + <h3>The page you were looking for doesn't exist.</h3> + <hr/> + <p>You may have mistyped the address or the page may have moved.</p> </body> </html> diff --git a/public/500.html b/public/500.html index 3be1cc259c0a0eb0acf6000249615059c7892200..5b78e3e38cba668fbe5e1739aa543beb98a22189 100644 --- a/public/500.html +++ b/public/500.html @@ -4,13 +4,10 @@ <title>We're sorry, but something went wrong (500)</title> <link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> </head> - <body> - <!-- This file lives in public/500.html --> <h1>500</h1> - <div> - <h2>We're sorry, but something went wrong.</h2> - <p>We've been notified about this issue and we'll take a look at it shortly.</p> - </div> + <h3>We're sorry, but something went wrong.</h3> + <hr/> + <p>We've been notified about this issue and we'll take a look at it shortly.</p> </body> </html> diff --git a/public/githost_error.html b/public/githost_error.html deleted file mode 100644 index b5258ce160ed8b0d638278892462260cce984c63..0000000000000000000000000000000000000000 --- a/public/githost_error.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>We're sorry, but we can't get access to your gitolite</title> - <style type="text/css"> - body { background-color: #EAEAEA; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 600px; - padding: 0 4em; - margin: 4em auto 0 auto; - } - h1 { font-size: 48px; color: #444; line-height: 1.5em; } - h2 { font-size: 24px; color: #666; line-height: 1.5em; } - h3, code { text-align:left; } - code pre { margin-left:40px; } - </style> -</head> - -<body> - <!-- This file lives in public/500.html --> - <div class="dialog"> - <h1>Gitolite Error</h1> - <h2>Application can't get access to your gitolite system.</h2> - <hr> - <h3> 1. Check 'config/gitlab.yml' for correct settings.</h3> - <h3> 2. Make sure web server user has access to gitolite. <a href="https://github.com/gitlabhq/gitlabhq/wiki/Gitolite">Setup tutorial</a></h3> - <h3> 3. Try: </h3> - <code> - <pre> -sudo chmod -R 770 /home/git/repositories/ -sudo chown -R git:git /home/git/repositories/ - </pre> - </code> - </div> -</body> -</html> diff --git a/public/static.css b/public/static.css index 6090d7b2abbab1477bfabf28c567c73f86931f1d..aa834553a1ca64ab8fdde45835f04947f259a334 100644 --- a/public/static.css +++ b/public/static.css @@ -1,57 +1,31 @@ -body { color: #666; text-align: center; font-family: arial, sans-serif; margin:0; padding:0; } -h1 { font-size: 48px; color: #444; line-height: 1.5em; } +body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + sans-serif; + margin:0; + width: 800px; + margin: auto; + font-size: 14px; +} +h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; +} h2 { font-size: 24px; color: #666; line-height: 1.5em; } -.alert-message { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - background-color: #eedc94; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94)); - background-image: -moz-linear-gradient(top, #fceec1, #eedc94); - background-image: -ms-linear-gradient(top, #fceec1, #eedc94); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94)); - background-image: -webkit-linear-gradient(top, #fceec1, #eedc94); - background-image: -o-linear-gradient(top, #fceec1, #eedc94); - background-image: linear-gradient(top, #fceec1, #eedc94); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFCEEC1', endColorstr='#FFEEDC94', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #eedc94 #eedc94 #e4c652; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) fadein(rgba(0, 0, 0, 0.1), 15%); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - border-width: 1px; - border-style: solid; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); -} -.alert-message .close { - margin-top: 1px; - *margin-top: 0; -} -.alert-message a { - font-weight: bold; - color: #404040; -} -.alert-message.danger p a, .alert-message.error p a, .alert-message.success p a, .alert-message.info p a { - color: #404040; -} -.alert-message h5 { - line-height: 18px; -} -.alert-message p { - margin-bottom: 0; -} -.alert-message div { - margin-top: 5px; - margin-bottom: 2px; +h3 { + color: #456; + font-size: 20px; + font-weight: normal; line-height: 28px; } -.alert-message.block-message.error { - background: #FDDFDE; - border-color: #FBC7C6; +hr { + margin: 18px 0; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; } - diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5aef4c676eeb9d3b04342f5a06507e93857efda5 --- /dev/null +++ b/spec/controllers/commit_controller_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe CommitController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:commit) { project.last_commit_for("master") } + + before do + sign_in(user) + + project.add_access(user, :read, :admin) + end + + describe "#show" do + shared_examples "export as" do |format| + it "should generally work" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response).to be_success + end + + it "should generate it" do + Commit.any_instance.should_receive(:"to_#{format}") + + get :show, project_id: project.code, id: commit.id, format: format + end + + it "should render it" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to eq(commit.send(:"to_#{format}")) + end + + it "should not escape Html" do + Commit.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to_not include('&') + expect(response.body).to_not include('>') + expect(response.body).to_not include('<') + expect(response.body).to_not include('"') + end + end + + describe "as diff" do + include_examples "export as", :diff + let(:format) { :diff } + + it "should really only be a git diff" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to start_with("diff --git") + end + end + + describe "as patch" do + include_examples "export as", :patch + let(:format) { :patch } + + it "should really be a git email patch" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to start_with("From #{commit.id}") + end + + it "should contain a git diff" do + get :show, project_id: project.code, id: commit.id, format: format + + expect(response.body).to match(/^diff --git/) + end + end + end +end diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb index bf335634da97788ed88e7bd3853272680f84a4ac..da33fd8a2b5f4fa1b588e557666260537d3930bd 100644 --- a/spec/controllers/commits_controller_spec.rb +++ b/spec/controllers/commits_controller_spec.rb @@ -13,7 +13,7 @@ describe CommitsController do describe "GET show" do context "as atom feed" do it "should render as atom" do - get :show, project_id: project.code, id: "master.atom" + get :show, project_id: project.path, id: "master.atom" response.should be_success response.content_type.should == 'application/atom+xml' end diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..7aebe06cf0c03f9d7cacd33f7aa63560510b33d8 --- /dev/null +++ b/spec/controllers/merge_requests_controller_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe MergeRequestsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request_with_diffs, project: project) } + + before do + sign_in(user) + project.add_access(user, :read, :admin) + MergeRequestsController.any_instance.stub(validates_merge_request: true) + end + + describe "#show" do + shared_examples "export as" do |format| + it "should generally work" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response).to be_success + end + + it "should generate it" do + MergeRequest.any_instance.should_receive(:"to_#{format}") + + get :show, project_id: project.code, id: merge_request.id, format: format + end + + it "should render it" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to eq(merge_request.send(:"to_#{format}")) + end + + it "should not escape Html" do + MergeRequest.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to_not include('&') + expect(response.body).to_not include('>') + expect(response.body).to_not include('<') + expect(response.body).to_not include('"') + end + end + + describe "as diff" do + include_examples "export as", :diff + let(:format) { :diff } + + it "should really only be a git diff" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to start_with("diff --git") + end + end + + describe "as patch" do + include_examples "export as", :patch + let(:format) { :patch } + + it "should really be a git email patch with commit" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}") + end + + # TODO: fix or remove + #it "should contain as many patches as there are commits" do + #get :show, project_id: project.code, id: merge_request.id, format: format + + #patch_count = merge_request.commits.count + #merge_request.commits.each_with_index do |commit, patch_num| + #expect(response.body).to match(/^From #{commit.id}/) + #expect(response.body).to match(/^Subject: \[PATCH #{patch_num}\/#{patch_count}\]/) + #end + #end + + it "should contain git diffs" do + get :show, project_id: project.code, id: merge_request.id, format: format + + expect(response.body).to match(/^diff --git/) + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 7c33f0ecc8b546ec7c4b48d8520fb8e9790dcba6..abc0d37470176e57eb8006e0af64dbb4fbbebb02 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -12,6 +12,7 @@ FactoryGirl.define do factory :user, aliases: [:author, :assignee, :owner] do email { Faker::Internet.email } name + username { Faker::Internet.user_name } password "123456" password_confirmation { password } @@ -25,19 +26,26 @@ FactoryGirl.define do factory :project do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } - code { name.downcase.gsub(/\s/, '_') } owner end factory :group do sequence(:name) { |n| "group#{n}" } - code { name.downcase.gsub(/\s/, '_') } + path { name.downcase.gsub(/\s/, '_') } + owner + type 'Group' + end + + factory :namespace do + sequence(:name) { |n| "group#{n}" } + path { name.downcase.gsub(/\s/, '_') } owner end factory :users_project do user project + project_access { UsersProject::MASTER } end factory :issue do @@ -63,7 +71,22 @@ FactoryGirl.define do closed true end + # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) + trait :with_diffs do + target_branch "bcf03b5d~3" + source_branch "bcf03b5d" + st_commits do + [Commit.new(project.repo.commit('bcf03b5d')), + Commit.new(project.repo.commit('bcf03b5d~1')), + Commit.new(project.repo.commit('bcf03b5d~2'))] + end + st_diffs do + project.repo.diff("bcf03b5d~3", "bcf03b5d") + end + end + factory :closed_merge_request, traits: [:closed] + factory :merge_request_with_diffs, traits: [:with_diffs] end factory :note do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index a94d5505a91ea710340c9db66a165d14f44753c6..ba1af08421b401b8b8f73c11cae3162e54de0842 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -43,7 +43,7 @@ describe ApplicationHelper do let(:user_email) { 'user@email.com' } it "should return a generic avatar path when Gravatar is disabled" do - Gitlab.config.stub(:disable_gravatar?).and_return(true) + Gitlab.config.gravatar.stub(:enabled).and_return(false) gravatar_icon(user_email).should == 'no_avatar.png' end @@ -51,14 +51,36 @@ describe ApplicationHelper do gravatar_icon('').should == 'no_avatar.png' end + it "should return default gravatar url" do + stub!(:request).and_return(double(:ssl? => false)) + gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') + end + it "should use SSL when appropriate" do stub!(:request).and_return(double(:ssl? => true)) gravatar_icon(user_email).should match('https://secure.gravatar.com') end + it "should return custom gravatar path when gravatar_url is set" do + stub!(:request).and_return(double(:ssl? => false)) + Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}') + gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' + end + it "should accept a custom size" do stub!(:request).and_return(double(:ssl? => false)) gravatar_icon(user_email, 64).should match(/\?s=64/) end + + it "should use default size when size is wrong" do + stub!(:request).and_return(double(:ssl? => false)) + gravatar_icon(user_email, nil).should match(/\?s=40/) + end + + it "should be case insensitive" do + stub!(:request).and_return(double(:ssl? => false)) + gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ") + end + end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index ec830e40ecdc13e60f3854f808ebc4d9f1ebd341..b792e0c86f6480150d99c13dc3e9f74718be626b 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe GitlabMarkdownHelper do let!(:project) { create(:project) } - let(:user) { create(:user, name: 'gfm') } + let(:user) { create(:user, username: 'gfm') } let(:commit) { CommitDecorator.decorate(project.commit) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, project: project) } @@ -81,11 +81,11 @@ describe GitlabMarkdownHelper do end describe "referencing a team member" do - let(:actual) { "@#{user.name} you are right." } + let(:actual) { "@#{user.username} you are right." } let(:expected) { project_team_member_path(project, member) } before do - project.users << user + project.add_access(user, :admin) end it "should link using a simple name" do @@ -103,18 +103,18 @@ describe GitlabMarkdownHelper do end it "should link with adjacent text" do - actual = "Mail the admin (@gfm)" + actual = "Mail the admin (@#{user.username})" gfm(actual).should match(expected) end it "should keep whitespace intact" do - actual = "Yes, @#{user.name} is right." - expected = /Yes, <a.+>@#{user.name}<\/a> is right/ + actual = "Yes, @#{user.username} is right." + expected = /Yes, <a.+>@#{user.username}<\/a> is right/ gfm(actual).should match(expected) end it "should not link with an invalid id" do - actual = expected = "@#{user.name.reverse} you are right." + actual = expected = "@#{user.username.reverse} you are right." gfm(actual).should == expected end @@ -314,12 +314,12 @@ describe GitlabMarkdownHelper do end it "should handle references in lists" do - project.users << user + project.add_access(user, :admin) - actual = "\n* dark: ##{issue.id}\n* light by @#{member.user_name}" + actual = "\n* dark: ##{issue.id}\n* light by @#{member.user.username}" markdown(actual).should match(%r{<li>dark: <a.+>##{issue.id}</a></li>}) - markdown(actual).should match(%r{<li>light by <a.+>@#{member.user_name}</a></li>}) + markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>}) end it "should handle references in <em>" do @@ -329,9 +329,11 @@ describe GitlabMarkdownHelper do end it "should leave code blocks untouched" do - markdown("\n some code from $#{snippet.id}\n here too\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div>" + helper.stub(:user_color_scheme_class).and_return(:white) - markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div>" + helper.markdown("\n some code from $#{snippet.id}\n here too\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>") + + helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>") end it "should leave inline code untouched" do diff --git a/spec/lib/gitolite_spec.rb b/spec/lib/gitolite_spec.rb index cc8ce8b2ccefac97dd6954e2659c89a543d4a227..8075b99ed99037ce637bcb510fbd66ec660f7a4d 100644 --- a/spec/lib/gitolite_spec.rb +++ b/spec/lib/gitolite_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Gitolite do it { should respond_to :create_repository } it { should respond_to :remove_repository } - it { gitolite.url_to_repo('diaspora').should == Gitlab.config.ssh_path + "diaspora.git" } + it { gitolite.url_to_repo('diaspora').should == Gitlab.config.gitolite.ssh_path_prefix + "diaspora.git" } it "should call config update" do gitolite_config.should_receive(:update_project!) diff --git a/spec/lib/project_mover_spec.rb b/spec/lib/project_mover_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2362bc2667b3175d1151784ac53d788134503a05 --- /dev/null +++ b/spec/lib/project_mover_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Gitlab::ProjectMover do + let(:base_path) { Rails.root.join('tmp', 'rspec-sandbox') } + + before do + FileUtils.rm_rf base_path if File.exists? base_path + + Gitlab.config.gitolite.stub(repos_path: base_path) + + @project = create(:project) + end + + after do + FileUtils.rm_rf base_path + end + + it "should move project to subdir" do + mk_dir base_path, '', @project.path + mover = Gitlab::ProjectMover.new(@project, '', 'opensource') + + mover.execute.should be_true + moved?('opensource', @project.path).should be_true + end + + it "should move project from one subdir to another" do + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', 'randx') + + mover.execute.should be_true + moved?('randx', @project.path).should be_true + end + + it "should move project from subdir to base" do + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + mover.execute.should be_true + moved?('', @project.path).should be_true + end + + it "should raise if destination exists" do + mk_dir base_path, '', @project.path + mk_dir base_path, 'vsizov', @project.path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) + end + + it "should raise if move failed" do + mk_dir base_path + mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') + + expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) + end + + + def mk_dir base_path, namespace = '', project_path = '' + FileUtils.mkdir_p File.join(base_path, namespace, project_path + ".git") + end + + def moved? namespace, path + File.exists?(File.join(base_path, namespace, path + '.git')) + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index b6b1769fc80a930dae4916cbe9ebeafc0c1e8ce5..58698eec9f432a8e2edf0da2cfe76307ccda5f1a 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -169,9 +169,7 @@ describe Notify do end describe 'project access changed' do - let(:project) { create(:project, - path: "Fuu", - code: "Fuu") } + let(:project) { create(:project) } let(:user) { create(:user) } let(:users_project) { create(:users_project, project: project, diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 49cb49db375fca6e30be6c4707f618fd66f644e9..82b46b68b64d5cb3e11848bdf22e47016ea29083 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -59,7 +59,7 @@ describe Event do end it { @event.push?.should be_true } - it { @event.allowed?.should be_true } + it { @event.proper?.should be_true } it { @event.new_branch?.should be_true } it { @event.tag?.should be_false } it { @event.branch_name.should == "master" } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 6ae2cb201693b7f37e2dbad57f8f922f3a577103..108bc303540b905119049dcdf97f2a5b14b7a609 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,13 +1,14 @@ # == Schema Information # -# Table name: groups +# Table name: namespaces # # id :integer not null, primary key # name :string(255) not null -# code :string(255) not null +# path :string(255) not null # owner_id :integer not null # created_at :datetime not null # updated_at :datetime not null +# type :string(255) # require 'spec_helper' @@ -18,7 +19,15 @@ describe Group do it { should have_many :projects } it { should validate_presence_of :name } it { should validate_uniqueness_of(:name) } - it { should validate_presence_of :code } - it { should validate_uniqueness_of(:code) } + it { should validate_presence_of :path } + it { should validate_uniqueness_of(:path) } it { should validate_presence_of :owner } + + describe :users do + it { group.users.should == [group.owner] } + end + + describe :human_name do + it { group.human_name.should == group.name } + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d70647f668da023c0fafab40b078b8906dcb8d84..a08494012544bc5349a4ac9e619b036f6a1941bb 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -42,7 +42,7 @@ describe MergeRequest do before do merge_request.stub(:commits) { [merge_request.project.commit] } - create(:note, noteable: merge_request.commits.first) + create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit') create(:note, noteable: merge_request) end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d0de4a7b7fb0eafa6a57c39f870f767530f0deff --- /dev/null +++ b/spec/models/namespace_spec.rb @@ -0,0 +1,78 @@ +# == Schema Information +# +# Table name: namespaces +# +# id :integer not null, primary key +# name :string(255) not null +# path :string(255) not null +# owner_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# type :string(255) +# + +require 'spec_helper' + +describe Namespace do + let!(:namespace) { create(:namespace) } + + it { should have_many :projects } + it { should validate_presence_of :name } + it { should validate_uniqueness_of(:name) } + it { should validate_presence_of :path } + it { should validate_uniqueness_of(:path) } + it { should validate_presence_of :owner } + + describe "Mass assignment" do + it { should allow_mass_assignment_of(:name) } + it { should allow_mass_assignment_of(:path) } + end + + describe "Respond to" do + it { should respond_to(:human_name) } + it { should respond_to(:to_param) } + end + + it { Namespace.global_id.should == 'GLN' } + + describe :to_param do + it { namespace.to_param.should == namespace.path } + end + + describe :human_name do + it { namespace.human_name.should == namespace.owner_name } + end + + describe :search do + before do + @namespace = create :namespace + end + + it { Namespace.search(@namespace.path).should == [@namespace] } + it { Namespace.search('unknown').should == [] } + end + + describe :move_dir do + before do + @namespace = create :namespace + @namespace.stub(path_changed?: true) + end + + it "should raise error when dirtory exists" do + expect { @namespace.move_dir }.to raise_error("Already exists") + end + + it "should move dir if path changed" do + new_path = @namespace.path + "_new" + @namespace.stub(path_was: @namespace.path) + @namespace.stub(path: new_path) + @namespace.move_dir.should be_true + end + end + + describe :rm_dir do + it "should remove dir" do + namespace.rm_dir.should be_true + end + end +end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 4f9352b9a144159883b1d39bb742f4e22be904c6..61aaf6455ebfda8e124644a0730b4ccce9037453 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -81,18 +81,18 @@ describe Note do describe "Commit notes" do before do @note = create(:note, - noteable_id: commit.id, + commit_id: commit.id, noteable_type: "Commit") end it "should be accessible through #noteable" do - @note.noteable_id.should == commit.id + @note.commit_id.should == commit.id @note.noteable.should be_a(Commit) @note.noteable.should == commit end it "should save a valid note" do - @note.noteable_id.should == commit.id + @note.commit_id.should == commit.id @note.noteable == commit end @@ -104,13 +104,13 @@ describe Note do describe "Pre-line commit notes" do before do @note = create(:note, - noteable_id: commit.id, + commit_id: commit.id, noteable_type: "Commit", line_code: "0_16_1") end it "should save a valid note" do - @note.noteable_id.should == commit.id + @note.commit_id.should == commit.id @note.noteable.id.should == commit.id end diff --git a/spec/models/project_hooks_spec.rb b/spec/models/project_hooks_spec.rb index 7c8f05b17a39f439f8002b9a796aa0c6c037baee..77adfe06cff1b9ab0eb1ae4761806b528ee3d2fd 100644 --- a/spec/models/project_hooks_spec.rb +++ b/spec/models/project_hooks_spec.rb @@ -91,7 +91,7 @@ describe Project, "Hooks" do subject { @data[:repository] } it { should include(name: project.name) } - it { should include(url: project.web_url) } + it { should include(url: project.url_to_repo) } it { should include(description: project.description) } it { should include(homepage: project.web_url) } end @@ -108,7 +108,7 @@ describe Project, "Hooks" do it { should include(id: @commit.id) } it { should include(message: @commit.safe_message) } it { should include(timestamp: @commit.date.xmlschema) } - it { should include(url: "#{Gitlab.config.url}/#{project.code}/commits/#{@commit.id}") } + it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.code}/commit/#{@commit.id}") } context "with a author" do subject { @data[:commits].first[:author] } diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb index 60f8d45c9c803c2df01f83fcc2912189c7b8fa3f..92c6bce08f6cff0e392874e6fa513a09d87f431b 100644 --- a/spec/models/project_security_spec.rb +++ b/spec/models/project_security_spec.rb @@ -4,38 +4,109 @@ describe Project do describe :authorization do before do @p1 = create(:project) + @u1 = create(:user) @u2 = create(:user) + @u3 = create(:user) + @u4 = @p1.chief + @abilities = Six.new @abilities << Ability end - describe "read access" do + let(:guest_actions) { Ability.project_guest_rules } + let(:report_actions) { Ability.project_report_rules } + let(:dev_actions) { Ability.project_dev_rules } + let(:master_actions) { Ability.project_master_rules } + let(:admin_actions) { Ability.project_admin_rules } + + describe "Non member rules" do + it "should deny for non-project users any actions" do + admin_actions.each do |action| + @abilities.allowed?(@u1, action, @p1).should be_false + end + end + end + + describe "Guest Rules" do + before do + @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::GUEST) + end + + it "should allow for project user any guest actions" do + guest_actions.each do |action| + @abilities.allowed?(@u2, action, @p1).should be_true + end + end + end + + describe "Report Rules" do before do @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::REPORTER) end - it { @abilities.allowed?(@u1, :read_project, @p1).should be_false } - it { @abilities.allowed?(@u2, :read_project, @p1).should be_true } + it "should allow for project user any report actions" do + report_actions.each do |action| + @abilities.allowed?(@u2, action, @p1).should be_true + end + end end - describe "write access" do + describe "Developer Rules" do + before do + @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::REPORTER) + @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::DEVELOPER) + end + + it "should deny for developer master-specific actions" do + [dev_actions - report_actions].each do |action| + @abilities.allowed?(@u2, action, @p1).should be_false + end + end + + it "should allow for project user any dev actions" do + dev_actions.each do |action| + @abilities.allowed?(@u3, action, @p1).should be_true + end + end + end + + describe "Master Rules" do before do @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::DEVELOPER) + @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::MASTER) end - it { @abilities.allowed?(@u1, :write_project, @p1).should be_false } - it { @abilities.allowed?(@u2, :write_project, @p1).should be_true } + it "should deny for developer master-specific actions" do + [master_actions - dev_actions].each do |action| + @abilities.allowed?(@u2, action, @p1).should be_false + end + end + + it "should allow for project user any master actions" do + master_actions.each do |action| + @abilities.allowed?(@u3, action, @p1).should be_true + end + end end - describe "admin access" do + describe "Admin Rules" do before do - @p1.users_projects.create(project: @p1, user: @u1, project_access: UsersProject::DEVELOPER) - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::MASTER) + @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::DEVELOPER) + @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::MASTER) end - it { @abilities.allowed?(@u1, :admin_project, @p1).should be_false } - it { @abilities.allowed?(@u2, :admin_project, @p1).should be_true } + it "should deny for masters admin-specific actions" do + [admin_actions - master_actions].each do |action| + @abilities.allowed?(@u2, action, @p1).should be_false + end + end + + it "should allow for project owner any admin actions" do + admin_actions.each do |action| + @abilities.allowed?(@u4, action, @p1).should be_true + end + end end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5bcab924496051bfd01f2344c798d163f0f03404..83a769760987b719e10892723c78ae797bed01d0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9,14 +9,13 @@ # created_at :datetime not null # updated_at :datetime not null # private_flag :boolean default(TRUE), not null -# code :string(255) # owner_id :integer # default_branch :string(255) # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null -# group_id :integer +# namespace_id :integer # require 'spec_helper' @@ -24,6 +23,7 @@ require 'spec_helper' describe Project do describe "Associations" do it { should belong_to(:group) } + it { should belong_to(:namespace) } it { should belong_to(:owner).class_name('User') } it { should have_many(:users) } it { should have_many(:events).dependent(:destroy) } @@ -40,6 +40,7 @@ describe Project do end describe "Mass assignment" do + it { should_not allow_mass_assignment_of(:namespace_id) } it { should_not allow_mass_assignment_of(:owner_id) } it { should_not allow_mass_assignment_of(:private_flag) } end @@ -58,9 +59,6 @@ describe Project do it { should ensure_length_of(:description).is_within(0..2000) } - it { should validate_presence_of(:code) } - it { should validate_uniqueness_of(:code) } - it { should ensure_length_of(:code).is_within(1..255) } # TODO: Formats it { should validate_presence_of(:owner) } @@ -131,6 +129,13 @@ describe Project do it { should respond_to(:execute_hooks) } it { should respond_to(:post_receive_data) } it { should respond_to(:trigger_post_receive) } + + # Namespaced Project Role + it { should respond_to(:transfer) } + it { should respond_to(:name_with_namespace) } + it { should respond_to(:namespace_owner) } + it { should respond_to(:chief) } + it { should respond_to(:path_with_namespace) } end describe 'modules' do @@ -138,11 +143,12 @@ describe Project do it { should include_module(PushObserver) } it { should include_module(Authority) } it { should include_module(Team) } + it { should include_module(NamespacedProject) } end it "should return valid url to repo" do project = Project.new(path: "somewhere") - project.url_to_repo.should == Gitlab.config.ssh_path + "somewhere.git" + project.url_to_repo.should == Gitlab.config.gitolite.ssh_path_prefix + "somewhere.git" end it "should return path to repo" do @@ -151,20 +157,8 @@ describe Project do end it "returns the full web URL for this repo" do - project = Project.new(code: "somewhere") - project.web_url.should == "#{Gitlab.config.url}/somewhere" - end - - describe :valid_repo? do - it "should be valid repo" do - project = create(:project) - project.valid_repo?.should be_true - end - - it "should be invalid repo" do - project = Project.new(name: "ok_name", path: "/INVALID_PATH/", code: "NEOK") - project.valid_repo?.should be_false - end + project = Project.new(path: "somewhere") + project.web_url.should == "#{Gitlab.config.gitlab.url}/somewhere" end describe "last_activity methods" do @@ -190,85 +184,6 @@ describe Project do end end - describe "fresh commits" do - let(:project) { create(:project) } - - it { project.fresh_commits(3).count.should == 3 } - it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } - it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } - end - - describe "commits_between" do - let(:project) { create(:project) } - - subject do - commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", - "8470d70da67355c9c009e4401746b1d5410af2e3") - commits.map { |c| c.id } - end - - it { should have(3).elements } - it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } - it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } - end - - describe "Git methods" do - let(:project) { create(:project) } - - describe :repo do - it "should return valid repo" do - project.repo.should be_kind_of(Grit::Repo) - end - - it "should return nil" do - lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) - end - - it "should return nil" do - lambda { Project.new.repo }.should raise_error(TypeError) - end - end - - describe :commit do - it "should return first head commit if without params" do - project.commit.id.should == project.repo.commits.first.id - end - - it "should return valid commit" do - project.commit(ValidCommit::ID).should be_valid_commit - end - - it "should return nil" do - project.commit("+123_4532530XYZ").should be_nil - end - end - - describe :tree do - before do - @commit = project.commit(ValidCommit::ID) - end - - it "should raise error w/o arguments" do - lambda { project.tree }.should raise_error - end - - it "should return root tree for commit" do - tree = project.tree(@commit) - tree.contents.size.should == ValidCommit::FILES_COUNT - tree.contents.map(&:name).should == ValidCommit::FILES - end - - it "should return root tree for commit with correct path" do - tree = project.tree(@commit, ValidCommit::C_FILE_PATH) - tree.contents.map(&:name).should == ValidCommit::C_FILES - end - - it "should return root tree for commit with incorrect path" do - project.tree(@commit, "invalid_path").should be_nil - end - end - end - describe :update_merge_requests do let(:project) { create(:project) } diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index 9d03b56cd8f563e1f0fb04dd0a508f6349399e3c..7ae483a4003c6caecc5cd6420a9358041b9db6de 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -56,7 +56,7 @@ describe SystemHook do user = create(:user) project = create(:project) with_resque do - project.users << user + project.add_access(user, :admin) end WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once end @@ -64,7 +64,7 @@ describe SystemHook do it "project_destroy hook" do user = create(:user) project = create(:project) - project.users << user + project.add_access(user, :admin) with_resque do project.users_projects.clear end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4ac699b1c453f760bb6432fa9ee84d95d7eb021e..d09484f8fe066e5c8df464d063d10e40ea481245 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -30,15 +30,17 @@ # locked_at :datetime # extern_uid :string(255) # provider :string(255) +# username :string(255) # require 'spec_helper' describe User do describe "Associations" do + it { should have_one(:namespace) } it { should have_many(:users_projects).dependent(:destroy) } it { should have_many(:projects) } - it { should have_many(:my_own_projects).class_name('Project') } + it { should have_many(:groups) } it { should have_many(:keys).dependent(:destroy) } it { should have_many(:events).class_name('Event').dependent(:destroy) } it { should have_many(:recent_events).class_name('Event') } @@ -55,6 +57,7 @@ describe User do end describe 'validations' do + it { should validate_presence_of(:username) } it { should validate_presence_of(:projects_limit) } it { should validate_numericality_of(:projects_limit) } it { should allow_value(0).for(:projects_limit) } @@ -63,6 +66,10 @@ describe User do it { should ensure_length_of(:bio).is_within(0..255) } end + describe 'modules' do + it { should include_module(Account) } + end + describe "Respond to" do it { should respond_to(:is_admin?) } it { should respond_to(:identifier) } diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb index 1f896324f18bea50f6fee576328d2adeb8fe58a2..a9a1857eb69829b21cc5b68da56d91096dd69473 100644 --- a/spec/models/users_project_spec.rb +++ b/spec/models/users_project_spec.rb @@ -29,6 +29,7 @@ describe UsersProject do it { should validate_uniqueness_of(:user_id).scoped_to(:project_id).with_message(/already exists/) } it { should validate_presence_of(:project) } + it { should ensure_inclusion_of(:project_access).in_array(UsersProject.access_roles.values) } end describe "Delegate methods" do diff --git a/spec/observers/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb index 0eec41f44e9dc1e9d9cda9246e921d90caa9f9d0..6af5d070888f4e92faad45c4436ade9ee795ca0b 100644 --- a/spec/observers/activity_observer_spec.rb +++ b/spec/observers/activity_observer_spec.rb @@ -34,15 +34,17 @@ describe ActivityObserver do it { @event.target.should == @issue } end - #describe "Issue commented" do - #before do - #@issue = create(:issue, project: project) - #@note = create(:note, noteable: @issue, project: project) - #@event = Event.last - #end - - #it_should_be_valid_event - #it { @event.action.should == Event::Commented } - #it { @event.target.should == @note } - #end + describe "Issue commented" do + before do + Note.observers.enable :activity_observer do + @issue = create(:issue, project: project) + @note = create(:note, noteable: @issue, project: project, author: @issue.author) + @event = Event.last + end + end + + it_should_be_valid_event + it { @event.action.should == Event::Commented } + it { @event.target.should == @note } + end end diff --git a/spec/observers/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb index 509c1d02b491c4f03d2bf59fb2d4c95c20b0aa01..bbffbd342db55a1d877ee653f3ef32b5efa3c38f 100644 --- a/spec/observers/issue_observer_spec.rb +++ b/spec/observers/issue_observer_spec.rb @@ -85,7 +85,7 @@ describe IssueObserver do it 'notification is delivered if the issue being closed' do issue.stub(:is_being_closed?).and_return(true) - Notify.should_receive(:issue_status_changed_email).twice + Notify.should_receive(:issue_status_changed_email).twice.and_return(stub(deliver: true)) Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') subject.after_update(issue) @@ -104,7 +104,7 @@ describe IssueObserver do issue_without_assignee.stub(:is_being_reassigned?).and_return(false) issue_without_assignee.stub(:is_being_closed?).and_return(true) issue_without_assignee.stub(:is_being_reopened?).and_return(false) - Notify.should_receive(:issue_status_changed_email).once + Notify.should_receive(:issue_status_changed_email).once.and_return(stub(deliver: true)) Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed') subject.after_update(issue_without_assignee) @@ -128,7 +128,7 @@ describe IssueObserver do it 'notification is delivered if the issue being reopened' do issue.stub(:is_being_reopened?).and_return(true) - Notify.should_receive(:issue_status_changed_email).twice + Notify.should_receive(:issue_status_changed_email).twice.and_return(stub(deliver: true)) Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') subject.after_update(issue) @@ -147,7 +147,7 @@ describe IssueObserver do issue_without_assignee.stub(:is_being_reassigned?).and_return(false) issue_without_assignee.stub(:is_being_closed?).and_return(false) issue_without_assignee.stub(:is_being_reopened?).and_return(true) - Notify.should_receive(:issue_status_changed_email).once + Notify.should_receive(:issue_status_changed_email).once.and_return(stub(deliver: true)) Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened') subject.after_update(issue_without_assignee) diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb index 08254f44026da78f9c408bc783e3289ca7b5411a..4ba0f05df5da4ccf15aff679055e78769863a15a 100644 --- a/spec/observers/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -13,7 +13,12 @@ describe UserObserver do end context 'when a new user is created' do - let(:user) { double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local') } + let(:user) { double(:user, id: 42, + password: 'P@ssword!', + name: 'John', + email: 'u@mail.local', + username: 'root', + create_namespace: true) } let(:notification) { double :notification } it 'sends an email' do diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb index cbe4224803371ffe451c918c1f1e5dfa4ff5db55..548f18938482d0a643a37503c7cd3b56d72f7018 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe UsersProjectObserver do let(:user) { create(:user) } - let(:project) { create(:project, - code: "Fuu", - path: "Fuu" ) } + let(:project) { create(:project) } let(:users_project) { create(:users_project, project: project, user: user )} diff --git a/spec/requests/admin/admin_hooks_spec.rb b/spec/requests/admin/admin_hooks_spec.rb index 3f35b2fd37d58d8ebe9cb95207b080ac28686970..bc0586b2712ce4a0c2a3495357b52d8c038e2df4 100644 --- a/spec/requests/admin/admin_hooks_spec.rb +++ b/spec/requests/admin/admin_hooks_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe "Admin::Hooks" do before do - @project = create(:project, - name: "LeGiT", - code: "LGT") + @project = create(:project) login_as :admin @system_hook = create(:system_hook) diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb index 43e39d7cbcd009ab29ba18a55f956604bbb0b04e..c9ddf1f45346fb23f83c9939d1c334cec12f0684 100644 --- a/spec/requests/admin/admin_projects_spec.rb +++ b/spec/requests/admin/admin_projects_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' describe "Admin::Projects" do before do - @project = create(:project, - name: "LeGiT", - code: "LGT") + @project = create(:project) login_as :admin end @@ -29,7 +27,7 @@ describe "Admin::Projects" do end it "should have project info" do - page.should have_content(@project.code) + page.should have_content(@project.path) page.should have_content(@project.name) end end @@ -41,67 +39,27 @@ describe "Admin::Projects" do end it "should have project edit page" do - page.should have_content("Project name") - page.should have_content("URL") + page.should have_content("Edit project") + page.should have_button("Save Project") end describe "Update project" do before do fill_in "project_name", with: "Big Bang" - fill_in "project_code", with: "BB1" click_button "Save Project" @project.reload end it "should show page with new data" do - page.should have_content("BB1") page.should have_content("Big Bang") end it "should change project entry" do @project.name.should == "Big Bang" - @project.code.should == "BB1" end end end - describe "GET /admin/projects/new" do - before do - visit admin_projects_path - click_link "New Project" - end - - it "should be correct path" do - current_path.should == new_admin_project_path - end - - it "should have labels for new project" do - page.should have_content("Project name is") - page.should have_content("Git Clone") - page.should have_content("URL") - end - end - - describe "POST /admin/projects" do - before do - visit new_admin_project_path - fill_in 'project_name', with: 'NewProject' - fill_in 'project_code', with: 'NPR' - fill_in 'project_path', with: 'gitlabhq_1' - expect { click_button "Create project" }.to change { Project.count }.by(1) - @project = Project.last - end - - it "should be correct path" do - current_path.should == admin_project_path(@project) - end - - it "should show project" do - page.should have_content(@project.name) - page.should have_content(@project.path) - end - end - describe "Add new team member" do before do @new_user = create(:user) diff --git a/spec/requests/admin/admin_users_spec.rb b/spec/requests/admin/admin_users_spec.rb index 9f43f07a476db7f73d1d79a0e45b3b970d57fee8..ca134c2d4f741b671cd165be67b4a76529043aae 100644 --- a/spec/requests/admin/admin_users_spec.rb +++ b/spec/requests/admin/admin_users_spec.rb @@ -23,6 +23,7 @@ describe "Admin::Users" do @password = "123ABC" visit new_admin_user_path fill_in "user_name", with: "Big Bang" + fill_in "user_username", with: "bang" fill_in "user_email", with: "bigbang@mail.com" fill_in "user_password", with: @password fill_in "user_password_confirmation", with: @password diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 6ea7e9b557924e3775d0b6de68b558e391e130ff..1850ecb95ccd7bb79b713ed4d1eb11806f61cf2b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues" do it "should return project issues" do - get api("/projects/#{project.code}/issues", user) + get api("/projects/#{project.path}/issues", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == issue.title @@ -37,7 +37,7 @@ describe Gitlab::API do describe "GET /projects/:id/issues/:issue_id" do it "should return a project issue by id" do - get api("/projects/#{project.code}/issues/#{issue.id}", user) + get api("/projects/#{project.path}/issues/#{issue.id}", user) response.status.should == 200 json_response['title'].should == issue.title end @@ -45,7 +45,7 @@ describe Gitlab::API do describe "POST /projects/:id/issues" do it "should create a new project issue" do - post api("/projects/#{project.code}/issues", user), + post api("/projects/#{project.path}/issues", user), title: 'new issue', labels: 'label, label2' response.status.should == 201 json_response['title'].should == 'new issue' @@ -56,7 +56,7 @@ describe Gitlab::API do describe "PUT /projects/:id/issues/:issue_id" do it "should update a project issue" do - put api("/projects/#{project.code}/issues/#{issue.id}", user), + put api("/projects/#{project.path}/issues/#{issue.id}", user), title: 'updated title', labels: 'label2', closed: 1 response.status.should == 200 json_response['title'].should == 'updated title' @@ -67,7 +67,7 @@ describe Gitlab::API do describe "DELETE /projects/:id/issues/:issue_id" do it "should delete a project issue" do - delete api("/projects/#{project.code}/issues/#{issue.id}", user) + delete api("/projects/#{project.path}/issues/#{issue.id}", user) response.status.should == 405 end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index e83f24671edf4da15c078ff06f985e2c05fd9bf5..43931aedcdacfd98831c22b68678fa7309e35349 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -11,14 +11,14 @@ describe Gitlab::API do describe "GET /projects/:id/merge_requests" do context "when unauthenticated" do it "should return authentication error" do - get api("/projects/#{project.code}/merge_requests") + get api("/projects/#{project.path}/merge_requests") response.status.should == 401 end end context "when authenticated" do it "should return an array of merge_requests" do - get api("/projects/#{project.code}/merge_requests", user) + get api("/projects/#{project.path}/merge_requests", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == merge_request.title @@ -28,7 +28,7 @@ describe Gitlab::API do describe "GET /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do - get api("/projects/#{project.code}/merge_request/#{merge_request.id}", user) + get api("/projects/#{project.path}/merge_request/#{merge_request.id}", user) response.status.should == 200 json_response['title'].should == merge_request.title end @@ -36,7 +36,7 @@ describe Gitlab::API do describe "POST /projects/:id/merge_requests" do it "should return merge_request" do - post api("/projects/#{project.code}/merge_requests", user), + post api("/projects/#{project.path}/merge_requests", user), title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user response.status.should == 201 json_response['title'].should == 'Test merge_request' @@ -45,7 +45,7 @@ describe Gitlab::API do describe "PUT /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do - put api("/projects/#{project.code}/merge_request/#{merge_request.id}", user), title: "New title" + put api("/projects/#{project.path}/merge_request/#{merge_request.id}", user), title: "New title" response.status.should == 200 json_response['title'].should == 'New title' end @@ -53,7 +53,7 @@ describe Gitlab::API do describe "POST /projects/:id/merge_request/:merge_request_id/comments" do it "should return comment" do - post api("/projects/#{project.code}/merge_request/#{merge_request.id}/comments", user), note: "My comment" + post api("/projects/#{project.path}/merge_request/#{merge_request.id}/comments", user), note: "My comment" response.status.should == 201 json_response['note'].should == 'My comment' end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 860825ab2dbf96ae2c2a187b990828a2b60ef616..dc96d46d89428f66462049c1ddaff1e01043cdf2 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::API do describe "GET /projects/:id/milestones" do it "should return project milestones" do - get api("/projects/#{project.code}/milestones", user) + get api("/projects/#{project.path}/milestones", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == milestone.title @@ -20,7 +20,7 @@ describe Gitlab::API do describe "GET /projects/:id/milestones/:milestone_id" do it "should return a project milestone by id" do - get api("/projects/#{project.code}/milestones/#{milestone.id}", user) + get api("/projects/#{project.path}/milestones/#{milestone.id}", user) response.status.should == 200 json_response['title'].should == milestone.title end @@ -28,7 +28,7 @@ describe Gitlab::API do describe "POST /projects/:id/milestones" do it "should create a new project milestone" do - post api("/projects/#{project.code}/milestones", user), + post api("/projects/#{project.path}/milestones", user), title: 'new milestone' response.status.should == 201 json_response['title'].should == 'new milestone' @@ -38,7 +38,7 @@ describe Gitlab::API do describe "PUT /projects/:id/milestones/:milestone_id" do it "should update a project milestone" do - put api("/projects/#{project.code}/milestones/#{milestone.id}", user), + put api("/projects/#{project.path}/milestones/#{milestone.id}", user), title: 'updated title' response.status.should == 200 json_response['title'].should == 'updated title' diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..681ba01558eca47112d443de4028cde09f512ade --- /dev/null +++ b/spec/requests/api/notes_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' + +describe Gitlab::API do + include ApiHelpers + + let(:user) { create(:user) } + let!(:project) { create(:project, owner: user) } + let!(:issue) { create(:issue, project: project, author: user) } + let!(:snippet) { create(:snippet, project: project, author: user) } + let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } + let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } + let!(:wall_note) { create(:note, project: project, author: user) } + before { project.add_access(user, :read) } + + describe "GET /projects/:id/notes" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects/#{project.id}/notes") + response.status.should == 401 + end + end + + context "when authenticated" do + it "should return project wall notes" do + get api("/projects/#{project.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == wall_note.note + end + end + end + + describe "GET /projects/:id/notes/:note_id" do + it "should return a wall note by id" do + get api("/projects/#{project.id}/notes/#{wall_note.id}", user) + response.status.should == 200 + json_response['body'].should == wall_note.note + end + end + + describe "POST /projects/:id/notes" do + it "should create a new wall note" do + post api("/projects/#{project.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + end + end + + describe "GET /projects/:id/noteable/:noteable_id/notes" do + context "when noteable is an Issue" do + it "should return an array of issue notes" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == issue_note.note + end + end + + context "when noteable is a Snippet" do + it "should return an array of snippet notes" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first['body'].should == snippet_note.note + end + end + end + + describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do + context "when noteable is an Issue" do + it "should return an issue note by id" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) + response.status.should == 200 + json_response['body'].should == issue_note.note + end + end + + context "when noteable is a Snippet" do + it "should return a snippet note by id" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) + response.status.should == 200 + json_response['body'].should == snippet_note.note + end + end + end + + describe "POST /projects/:id/noteable/:noteable_id/notes" do + context "when noteable is an Issue" do + it "should create a new issue note" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + json_response['author']['email'].should == user.email + end + end + + context "when noteable is a Snippet" do + it "should create a new snippet note" do + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' + response.status.should == 201 + json_response['body'].should == 'hi!' + json_response['author']['email'].should == user.email + end + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index d24ce43d3f2408a88d8a726fd7f6898c09db26b5..60cc75f5291a0ca9ca66b7e826e90f2e302eb1c4 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::API do end describe "POST /projects" do - it "should create new project without code and path" do + it "should create new project without path" do expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) end @@ -53,8 +53,6 @@ describe Gitlab::API do it "should assign attributes to project" do project = attributes_for(:project, { - path: 'path', - code: 'code', description: Faker::Lorem.sentence, default_branch: 'stable', issues_enabled: false, @@ -66,6 +64,7 @@ describe Gitlab::API do post api("/projects", user), project project.each_pair do |k,v| + next if k == :path json_response[k.to_s].should == v end end @@ -79,8 +78,8 @@ describe Gitlab::API do json_response['owner']['email'].should == user.email end - it "should return a project by code name" do - get api("/projects/#{project.code}", user) + it "should return a project by path name" do + get api("/projects/#{project.path}", user) response.status.should == 200 json_response['name'].should == project.name end @@ -94,7 +93,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches" do it "should return an array of project branches" do - get api("/projects/#{project.code}/repository/branches", user) + get api("/projects/#{project.path}/repository/branches", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name @@ -103,7 +102,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do - get api("/projects/#{project.code}/repository/branches/new_design", user) + get api("/projects/#{project.path}/repository/branches/new_design", user) response.status.should == 200 json_response['name'].should == 'new_design' @@ -113,17 +112,25 @@ describe Gitlab::API do describe "GET /projects/:id/members" do it "should return project team members" do - get api("/projects/#{project.code}/members", user) + get api("/projects/#{project.path}/members", user) response.status.should == 200 json_response.should be_an Array json_response.count.should == 2 json_response.first['email'].should == user.email end + + it "finds team members with query string" do + get api("/projects/#{project.path}/members", user), query: user.username + response.status.should == 200 + json_response.should be_an Array + json_response.count.should == 1 + json_response.first['email'].should == user.email + end end describe "GET /projects/:id/members/:user_id" do it "should return project team member" do - get api("/projects/#{project.code}/members/#{user.id}", user) + get api("/projects/#{project.path}/members/#{user.id}", user) response.status.should == 200 json_response['email'].should == user.email json_response['access_level'].should == UsersProject::MASTER @@ -133,7 +140,7 @@ describe Gitlab::API do describe "POST /projects/:id/members" do it "should add user to project team" do expect { - post api("/projects/#{project.code}/members", user), user_id: user2.id, + post api("/projects/#{project.path}/members", user), user_id: user2.id, access_level: UsersProject::DEVELOPER }.to change { UsersProject.count }.by(1) @@ -145,7 +152,7 @@ describe Gitlab::API do describe "PUT /projects/:id/members/:user_id" do it "should update project team member" do - put api("/projects/#{project.code}/members/#{user3.id}", user), access_level: UsersProject::MASTER + put api("/projects/#{project.path}/members/#{user3.id}", user), access_level: UsersProject::MASTER response.status.should == 200 json_response['email'].should == user3.email json_response['access_level'].should == UsersProject::MASTER @@ -155,14 +162,14 @@ describe Gitlab::API do describe "DELETE /projects/:id/members/:user_id" do it "should remove user from project team" do expect { - delete api("/projects/#{project.code}/members/#{user3.id}", user) + delete api("/projects/#{project.path}/members/#{user3.id}", user) }.to change { UsersProject.count }.by(-1) end end describe "GET /projects/:id/hooks" do it "should return project hooks" do - get api("/projects/#{project.code}/hooks", user) + get api("/projects/#{project.path}/hooks", user) response.status.should == 200 @@ -174,7 +181,7 @@ describe Gitlab::API do describe "GET /projects/:id/hooks/:hook_id" do it "should return a project hook" do - get api("/projects/#{project.code}/hooks/#{hook.id}", user) + get api("/projects/#{project.path}/hooks/#{hook.id}", user) response.status.should == 200 json_response['url'].should == hook.url end @@ -183,7 +190,7 @@ describe Gitlab::API do describe "POST /projects/:id/hooks" do it "should add hook to project" do expect { - post api("/projects/#{project.code}/hooks", user), + post api("/projects/#{project.path}/hooks", user), "url" => "http://example.com" }.to change {project.hooks.count}.by(1) end @@ -191,7 +198,7 @@ describe Gitlab::API do describe "PUT /projects/:id/hooks/:hook_id" do it "should update an existing project hook" do - put api("/projects/#{project.code}/hooks/#{hook.id}", user), + put api("/projects/#{project.path}/hooks/#{hook.id}", user), url: 'http://example.org' response.status.should == 200 json_response['url'].should == 'http://example.org' @@ -202,7 +209,7 @@ describe Gitlab::API do describe "DELETE /projects/:id/hooks" do it "should delete hook from project" do expect { - delete api("/projects/#{project.code}/hooks", user), + delete api("/projects/#{project.path}/hooks", user), hook_id: hook.id }.to change {project.hooks.count}.by(-1) end @@ -210,7 +217,7 @@ describe Gitlab::API do describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do - get api("/projects/#{project.code}/repository/tags", user) + get api("/projects/#{project.path}/repository/tags", user) response.status.should == 200 json_response.should be_an Array json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name @@ -222,7 +229,7 @@ describe Gitlab::API do before { project.add_access(user2, :read) } it "should return project commits" do - get api("/projects/#{project.code}/repository/commits", user) + get api("/projects/#{project.path}/repository/commits", user) response.status.should == 200 json_response.should be_an Array @@ -232,7 +239,7 @@ describe Gitlab::API do context "unauthorized user" do it "should not return project commits" do - get api("/projects/#{project.code}/repository/commits") + get api("/projects/#{project.path}/repository/commits") response.status.should == 401 end end @@ -240,7 +247,7 @@ describe Gitlab::API do describe "GET /projects/:id/snippets" do it "should return an array of project snippets" do - get api("/projects/#{project.code}/snippets", user) + get api("/projects/#{project.path}/snippets", user) response.status.should == 200 json_response.should be_an Array json_response.first['title'].should == snippet.title @@ -249,7 +256,7 @@ describe Gitlab::API do describe "GET /projects/:id/snippets/:snippet_id" do it "should return a project snippet" do - get api("/projects/#{project.code}/snippets/#{snippet.id}", user) + get api("/projects/#{project.path}/snippets/#{snippet.id}", user) response.status.should == 200 json_response['title'].should == snippet.title end @@ -257,7 +264,7 @@ describe Gitlab::API do describe "POST /projects/:id/snippets" do it "should create a new project snippet" do - post api("/projects/#{project.code}/snippets", user), + post api("/projects/#{project.path}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test' response.status.should == 201 json_response['title'].should == 'api test' @@ -266,7 +273,7 @@ describe Gitlab::API do describe "PUT /projects/:id/snippets/:shippet_id" do it "should update an existing project snippet" do - put api("/projects/#{project.code}/snippets/#{snippet.id}", user), + put api("/projects/#{project.path}/snippets/#{snippet.id}", user), code: 'updated code' response.status.should == 200 json_response['title'].should == 'example' @@ -277,31 +284,31 @@ describe Gitlab::API do describe "DELETE /projects/:id/snippets/:snippet_id" do it "should delete existing project snippet" do expect { - delete api("/projects/#{project.code}/snippets/#{snippet.id}", user) + delete api("/projects/#{project.path}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) end end describe "GET /projects/:id/snippets/:snippet_id/raw" do it "should get a raw project snippet" do - get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user) + get api("/projects/#{project.path}/snippets/#{snippet.id}/raw", user) response.status.should == 200 end end describe "GET /projects/:id/:sha/blob" do it "should get the raw file contents" do - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user) + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.md", user) response.status.should == 200 end it "should return 404 for invalid branch_name" do - get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) + get api("/projects/#{project.path}/repository/commits/invalid_branch_name/blob?filepath=README.md", user) response.status.should == 404 end it "should return 404 for invalid file" do - get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user) + get api("/projects/#{project.path}/repository/commits/master/blob?filepath=README.invalid", user) response.status.should == 404 end end diff --git a/spec/requests/gitlab_flavored_markdown_spec.rb b/spec/requests/gitlab_flavored_markdown_spec.rb index aedd435e61723e6a340d80c62e3d2a87c1d8cb9d..7f61c6aaa73dd20d68b9f388bf876544bb7570a9 100644 --- a/spec/requests/gitlab_flavored_markdown_spec.rb +++ b/spec/requests/gitlab_flavored_markdown_spec.rb @@ -6,7 +6,7 @@ describe "Gitlab Flavored Markdown" do let(:merge_request) { create(:merge_request, project: project) } let(:fred) do u = create(:user, name: "fred") - project.users << u + project.add_access(u, :admin) u end @@ -19,7 +19,7 @@ describe "Gitlab Flavored Markdown" do @test_file = "gfm_test_file" i.add(@test_file, "foo\nbar\n") # add commit with gfm - i.commit("fix ##{issue.id}\n\nask @#{fred.name} for details", head: @branch_name) + i.commit("fix ##{issue.id}\n\nask @#{fred.username} for details", head: @branch_name) # add test tag @tag_name = "gfm-test-tag" @@ -56,7 +56,7 @@ describe "Gitlab Flavored Markdown" do it "should render description in commits#show" do visit project_commit_path(project, commit) - page.should have_link("@#{fred.name}") + page.should have_link("@#{fred.username}") end it "should render title in refs#tree", js: true do @@ -93,7 +93,7 @@ describe "Gitlab Flavored Markdown" do assignee: @user, project: project, title: "fix ##{@other_issue.id}", - description: "ask @#{fred.name} for details") + description: "ask @#{fred.username} for details") end it "should render subject in issues#index" do @@ -111,7 +111,7 @@ describe "Gitlab Flavored Markdown" do it "should render details in issues#show" do visit project_issue_path(project, @issue) - page.should have_link("@#{fred.name}") + page.should have_link("@#{fred.username}") end end @@ -142,7 +142,7 @@ describe "Gitlab Flavored Markdown" do @milestone = create(:milestone, project: project, title: "fix ##{issue.id}", - description: "ask @#{fred.name} for details") + description: "ask @#{fred.username} for details") end it "should render title in milestones#index" do @@ -160,7 +160,7 @@ describe "Gitlab Flavored Markdown" do it "should render description in milestones#show" do visit project_milestone_path(project, @milestone) - page.should have_link("@#{fred.name}") + page.should have_link("@#{fred.username}") end end @@ -197,18 +197,6 @@ describe "Gitlab Flavored Markdown" do page.should have_link("##{issue.id}") end - - it "should render in wikis#index", js: true do - visit project_wiki_path(project, :index) - fill_in "Title", with: 'Test title' - fill_in "Content", with: '[link test](test)' - click_on "Save" - - fill_in "note_note", with: "see ##{issue.id}" - click_button "Add Comment" - - page.should have_link("##{issue.id}") - end end diff --git a/spec/requests/issues_spec.rb b/spec/requests/issues_spec.rb index ff4d4c8b8aefc86cd4c4dd54ea8a4a49aef9f587..0814108523ba71503728565656e7ee9ff15bb1d4 100644 --- a/spec/requests/issues_spec.rb +++ b/spec/requests/issues_spec.rb @@ -11,7 +11,7 @@ describe "Issues" do project.add_access(user2, :read, :write) end - describe "Edit issue", js: true do + describe "Edit issue" do let!(:issue) do create(:issue, author: @user, @@ -79,18 +79,6 @@ describe "Issues" do page.should have_content 'foobar2' page.should_not have_content 'gitlab' end - - it "should return all results if term has been cleared" do - visit project_issues_path(project) - fill_in "issue_search", with: "foobar" - # Reset the search field and trigger loading the issues - fill_in "issue_search", with: "" - page.execute_script("$('#issue_search').keyup();"); - - page.should have_content 'foobar' - page.should have_content 'foobar2' - page.should have_content 'gitlab' - end end describe "Filter issue" do @@ -103,13 +91,13 @@ describe "Issues" do title: title) end - issue = Issue.first # with title 'foobar' - issue.milestone = create(:milestone, project: project) - issue.assignee = nil - issue.save + @issue = Issue.first # with title 'foobar' + @issue.milestone = create(:milestone, project: project) + @issue.assignee = nil + @issue.save end - let(:issue) { Issue.first } + let(:issue) { @issue } it "should allow filtering by issues with no specified milestone" do visit project_issues_path(project, milestone_id: '0') diff --git a/spec/requests/projects_spec.rb b/spec/requests/projects_spec.rb index c44bea89f969de17e09874ce4cf7806650575b13..e097f0805fc1242fb310ab1c39b1c007591d1921 100644 --- a/spec/requests/projects_spec.rb +++ b/spec/requests/projects_spec.rb @@ -3,16 +3,6 @@ require 'spec_helper' describe "Projects" do before { login_as :user } - describe 'GET /project/new' do - it "should work autocomplete", :js => true do - visit new_project_path - - fill_in 'project_name', with: 'Awesome' - find("#project_path").value.should == 'awesome' - find("#project_code").value.should == 'awesome' - end - end - describe "GET /projects/show" do before do @project = create(:project, owner: @user) @@ -53,7 +43,6 @@ describe "Projects" do visit edit_project_path(@project) fill_in 'project_name', with: 'Awesome' - fill_in 'project_code', with: 'gitlabhq' click_button "Save" @project = @project.reload end @@ -69,7 +58,7 @@ describe "Projects" do describe "DELETE /projects/:id" do before do - @project = create(:project) + @project = create(:project, owner: @user) @project.add_access(@user, :read, :admin) visit edit_project_path(@project) end diff --git a/spec/requests/security/profile_access_spec.rb b/spec/requests/security/profile_access_spec.rb index 8562b8e78af0e2fc34b1d96ce3ee392b94c8f315..f854f3fb066c9ae58c2abb29c47997923677918b 100644 --- a/spec/requests/security/profile_access_spec.rb +++ b/spec/requests/security/profile_access_spec.rb @@ -29,7 +29,16 @@ describe "Users Security" do end describe "GET /profile/account" do - subject { profile_account_path } + subject { account_profile_path } + + it { should be_allowed_for @u1 } + it { should be_allowed_for :admin } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /profile/design" do + subject { design_profile_path } it { should be_allowed_for @u1 } it { should be_allowed_for :admin } diff --git a/spec/requests/snippets_spec.rb b/spec/requests/snippets_spec.rb index 9ef217ba62c4fc182a0859b49171a42e099bc7c8..b231b940a84bfd778db6512e04b382b62b308126 100644 --- a/spec/requests/snippets_spec.rb +++ b/spec/requests/snippets_spec.rb @@ -48,11 +48,11 @@ describe "Snippets" do page.current_path.should == new_project_snippet_path(project) end - describe "fill in" do + describe "fill in", js: true do before do fill_in "snippet_title", with: "login function" fill_in "snippet_file_name", with: "test.rb" - fill_in "snippet_content", with: "def login; end" + page.execute_script("editor.insert('def login; end');") end it { expect { click_button "Save" }.to change {Snippet.count}.by(1) } @@ -83,7 +83,6 @@ describe "Snippets" do before do fill_in "snippet_title", with: "login function" fill_in "snippet_file_name", with: "test.rb" - fill_in "snippet_content", with: "def login; end" end it { expect { click_button "Save" }.to_not change {Snippet.count} } diff --git a/spec/roles/account_role_spec.rb b/spec/roles/account_role_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4b214551453e0f123a5c5be3bc66aa770cc569d4 --- /dev/null +++ b/spec/roles/account_role_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe User, "Account" do + describe 'normal user' do + let(:user) { create(:user, name: 'John Smith') } + + it { user.is_admin?.should be_false } + it { user.require_ssh_key?.should be_true } + it { user.can_create_group?.should be_false } + it { user.can_create_project?.should be_true } + it { user.first_name.should == 'John' } + end + + describe 'blocking user' do + let(:user) { create(:user, name: 'John Smith') } + + it "should block user" do + user.block + user.blocked.should be_true + end + end + + describe 'projects' do + before do + ActiveRecord::Base.observers.enable(:user_observer) + @user = create :user + @project = create :project, namespace: @user.namespace + end + + it { @user.authorized_projects.should include(@project) } + it { @user.my_own_projects.should include(@project) } + end + + describe 'namespaced' do + before do + ActiveRecord::Base.observers.enable(:user_observer) + @user = create :user + @project = create :project, namespace: @user.namespace + end + + it { @user.several_namespaces?.should be_false } + it { @user.namespaces.should == [@user.namespace] } + end +end diff --git a/spec/roles/repository_spec.rb b/spec/roles/repository_spec.rb index 3507585aa8d876f3e289a2cbd7b803b886a522cd..e1d01cbfeafe377d1887728a49fa46619d236e9f 100644 --- a/spec/roles/repository_spec.rb +++ b/spec/roles/repository_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Project, "Repository" do - let(:project) { build(:project) } + let(:project) { create(:project) } describe "#empty_repo?" do it "should return true if the repo doesn't exist" do @@ -69,4 +69,91 @@ describe Project, "Repository" do project.root_ref?('stable').should be_false end end + + describe :repo do + it "should return valid repo" do + project.repo.should be_kind_of(Grit::Repo) + end + + it "should return nil" do + lambda { Project.new(path: "invalid").repo }.should raise_error(Grit::NoSuchPathError) + end + + it "should return nil" do + lambda { Project.new.repo }.should raise_error(TypeError) + end + end + + describe :commit do + it "should return first head commit if without params" do + project.commit.id.should == project.repo.commits.first.id + end + + it "should return valid commit" do + project.commit(ValidCommit::ID).should be_valid_commit + end + + it "should return nil" do + project.commit("+123_4532530XYZ").should be_nil + end + end + + describe :tree do + before do + @commit = project.commit(ValidCommit::ID) + end + + it "should raise error w/o arguments" do + lambda { project.tree }.should raise_error + end + + it "should return root tree for commit" do + tree = project.tree(@commit) + tree.contents.size.should == ValidCommit::FILES_COUNT + tree.contents.map(&:name).should == ValidCommit::FILES + end + + it "should return root tree for commit with correct path" do + tree = project.tree(@commit, ValidCommit::C_FILE_PATH) + tree.contents.map(&:name).should == ValidCommit::C_FILES + end + + it "should return root tree for commit with incorrect path" do + project.tree(@commit, "invalid_path").should be_nil + end + end + + describe "fresh commits" do + let(:project) { create(:project) } + + it { project.fresh_commits(3).count.should == 3 } + it { project.fresh_commits.first.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" } + it { project.fresh_commits.last.id.should == "f403da73f5e62794a0447aca879360494b08f678" } + end + + describe "commits_between" do + let(:project) { create(:project) } + + subject do + commits = project.commits_between("3a4b4fb4cde7809f033822a171b9feae19d41fff", + "8470d70da67355c9c009e4401746b1d5410af2e3") + commits.map { |c| c.id } + end + + it { should have(3).elements } + it { should include("f0f14c8eaba69ebddd766498a9d0b0e79becd633") } + it { should_not include("bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a") } + end + + describe :valid_repo? do + it "should be valid repo" do + project = create(:project) + project.valid_repo?.should be_true + end + + it "should be invalid repo" do + project = Project.new(name: "ok_name", path: "/INVALID_PATH/", path: "NEOK") + project.valid_repo?.should be_false + end + end end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 60261c7ae7102139540b931a4f72fddef0dfd93a..fb26bf98d0ff3631c7587deb01dd9f2c94565e63 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -78,14 +78,6 @@ describe Admin::ProjectsController, "routing" do get("/admin/projects").should route_to('admin/projects#index') end - it "to #create" do - post("/admin/projects").should route_to('admin/projects#create') - end - - it "to #new" do - get("/admin/projects/new").should route_to('admin/projects#new') - end - it "to #edit" do get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab') end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index dc687d2a7ac9f0581cae85d37b8b146c8dd9db8b..09e11588164166766b66debe731eb73f7e6a6d72 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -208,7 +208,6 @@ end # diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) merge_requests#diffs # automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) merge_requests#automerge # automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) merge_requests#automerge_check -# raw_project_merge_request GET /:project_id/merge_requests/:id/raw(.:format) merge_requests#raw # branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) merge_requests#branch_from # branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) merge_requests#branch_to # project_merge_requests GET /:project_id/merge_requests(.:format) merge_requests#index @@ -231,10 +230,6 @@ describe MergeRequestsController, "routing" do get("/gitlabhq/merge_requests/1/automerge_check").should route_to('merge_requests#automerge_check', project_id: 'gitlabhq', id: '1') end - it "to #raw" do - get("/gitlabhq/merge_requests/1/raw").should route_to('merge_requests#raw', project_id: 'gitlabhq', id: '1') - end - it "to #branch_from" do get("/gitlabhq/merge_requests/branch_from").should route_to('merge_requests#branch_from', project_id: 'gitlabhq') end @@ -243,8 +238,14 @@ describe MergeRequestsController, "routing" do get("/gitlabhq/merge_requests/branch_to").should route_to('merge_requests#branch_to', project_id: 'gitlabhq') end + it "to #show" do + get("/gitlabhq/merge_requests/1.diff").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'diff') + get("/gitlabhq/merge_requests/1.patch").should route_to('merge_requests#show', project_id: 'gitlabhq', id: '1', format: 'patch') + end + it_behaves_like "RESTful project resources" do let(:controller) { 'merge_requests' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end @@ -285,6 +286,7 @@ end describe CommitController, "routing" do it "to #show" do get("/gitlabhq/commit/4246fb").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb') + get("/gitlabhq/commit/4246fb.diff").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'diff') get("/gitlabhq/commit/4246fb.patch").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fb', format: 'patch') get("/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('commit#show', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') end @@ -324,6 +326,7 @@ end describe MilestonesController, "routing" do it_behaves_like "RESTful project resources" do let(:controller) { 'milestones' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end @@ -359,6 +362,7 @@ describe IssuesController, "routing" do it_behaves_like "RESTful project resources" do let(:controller) { 'issues' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index cb8dbf373baa82f9c0eb49bba48ae7d7dc1dcd10..57fd70e7497cd912e774b7a011c617f7ca7d5faa 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -33,6 +33,7 @@ end # help_system_hooks GET /help/system_hooks(.:format) help#system_hooks # help_markdown GET /help/markdown(.:format) help#markdown # help_ssh GET /help/ssh(.:format) help#ssh +# help_raketasks GET /help/raketasks(.:format) help#raketasks describe HelpController, "routing" do it "to #index" do get("/help").should route_to('help#index') @@ -65,6 +66,10 @@ describe HelpController, "routing" do it "to #ssh" do get("/help/ssh").should route_to('help#ssh') end + + it "to #raketasks" do + get("/help/raketasks").should route_to('help#raketasks') + end end # errors_githost GET /errors/githost(.:format) errors#githost @@ -82,37 +87,25 @@ end # profile GET /profile(.:format) profile#show # profile_design GET /profile/design(.:format) profile#design # profile_update PUT /profile/update(.:format) profile#update -describe ProfileController, "routing" do +describe ProfilesController, "routing" do it "to #account" do - get("/profile/account").should route_to('profile#account') + get("/profile/account").should route_to('profiles#account') end it "to #history" do - get("/profile/history").should route_to('profile#history') - end - - it "to #password_update" do - put("/profile/password").should route_to('profile#password_update') - end - - it "to #token" do - get("/profile/token").should route_to('profile#token') + get("/profile/history").should route_to('profiles#history') end it "to #reset_private_token" do - put("/profile/reset_private_token").should route_to('profile#reset_private_token') + put("/profile/reset_private_token").should route_to('profiles#reset_private_token') end it "to #show" do - get("/profile").should route_to('profile#show') + get("/profile").should route_to('profiles#show') end it "to #design" do - get("/profile/design").should route_to('profile#design') - end - - it "to #update" do - put("/profile/update").should route_to('profile#update') + get("/profile/design").should route_to('profiles#design') end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ace5ca00cc18c307654a7c7858d7a4d1805a4873..9f066c0e7f32727a1845747e85d2470f67fe42fd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -40,5 +40,10 @@ RSpec.configure do |config| # !!! Observers disabled by default in tests ActiveRecord::Base.observers.disable(:all) # ActiveRecord::Base.observers.enable(:all) + + # Use tmp dir for FS manipulations + Gitlab.config.gitolite.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path')) + FileUtils.rm_rf Gitlab.config.gitolite.repos_path + FileUtils.mkdir_p Gitlab.config.gitolite.repos_path end end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb index 7d9011971dd1a91482ff11c2d63ac156993fbdbc..c4514bf38be20c5b7921a04d8feb79b1b4d7341e 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/api_helpers.rb @@ -18,7 +18,7 @@ module ApiHelpers # # Returns the relative path to the requested API resource def api(path, user = nil) - "/api/#{Gitlab::API::VERSION}#{path}" + + "/api/#{Gitlab::API.version}#{path}" + # Normalize query string (path.index('?') ? '' : '?') + diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b17521e01486e62ef74fe279e8614c0a223f2f7a --- /dev/null +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:app namespace rake task' do + before :all do + Rake.application.rake_require "tasks/gitlab/backup" + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe 'backup_restore' do + before do + # avoid writing task output to spec progress + $stdout.stub :write + end + + let :run_rake_task do + Rake::Task["gitlab:backup:restore"].reenable + Rake.application.invoke_task "gitlab:backup:restore" + end + + context 'gitlab version' do + before do + Dir.stub :glob => [] + Dir.stub :chdir + File.stub :exists? => true + Kernel.stub :system => true + end + + let(:gitlab_version) { %x{git rev-parse HEAD}.gsub(/\n/,"") } + + it 'should fail on mismach' do + YAML.stub :load_file => {:gitlab_version => gitlab_version.reverse} + expect { run_rake_task }.to raise_error SystemExit + end + + it 'should invoke restoration on mach' do + YAML.stub :load_file => {:gitlab_version => gitlab_version} + Rake::Task["gitlab:backup:db:restore"].should_receive :invoke + Rake::Task["gitlab:backup:repo:restore"].should_receive :invoke + expect { run_rake_task }.to_not raise_error SystemExit + end + end + + end # backup_restore task +end # gitlab:app namespace diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index bbc91f4474af274c6812d0c6906af16698c638be..26b461c3825181df7f96e566cac7e82052619b15 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -14,8 +14,8 @@ describe PostReceive do let(:key_id) { key.identifier } it "fetches the correct project" do - Project.should_receive(:find_by_path).with(project.path).and_return(project) - PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id) + Project.should_receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) + PostReceive.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) end it "does not run if the author is not in the project" do @@ -24,17 +24,21 @@ describe PostReceive do project.should_not_receive(:observe_push) project.should_not_receive(:execute_hooks) - PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false + PostReceive.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false end it "asks the project to trigger all hooks" do - Project.stub(find_by_path: project) + Project.stub(find_with_namespace: project) project.should_receive(:execute_hooks) project.should_receive(:execute_services) project.should_receive(:update_merge_requests) project.should_receive(:observe_push) - PostReceive.perform(project.path, 'sha-old', 'sha-new', 'refs/heads/master', key_id) + PostReceive.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) end end + + def pwd(project) + File.join(Gitlab.config.gitolite.repos_path, project.path_with_namespace) + end end diff --git a/vendor/assets/javascripts/branch-graph.js b/vendor/assets/javascripts/branch-graph.js index e8699bdfbf2f5286a5233cbe0bef4ccb53b6f02e..a7e1e152bd2e7d70090ac0d33474449e62313fac 100644 --- a/vendor/assets/javascripts/branch-graph.js +++ b/vendor/assets/javascripts/branch-graph.js @@ -1,181 +1,255 @@ -var commits = {}, - comms = {}, - pixelsX = [], - pixelsY = [], - mmax = Math.max, - mtime = 0, - mspace = 0, - parents = {}, - ii = 0, - colors = ["#000"]; +!function(){ -function initGraph(){ - commits = chunk1.commits; - ii = commits.length; - for (var i = 0; i < ii; i++) { - for (var j = 0, jj = commits[i].parents.length; j < jj; j++) { - parents[commits[i].parents[j][0]] = true; + var BranchGraph = function(element, options){ + this.element = element; + this.options = options; + + this.preparedCommits = {}; + this.mtime = 0; + this.mspace = 0; + this.parents = {}; + this.colors = ["#000"]; + + this.load(); + }; + + BranchGraph.prototype.load = function(){ + $.ajax({ + url: this.options.url, + method: 'get', + dataType: 'json', + success: $.proxy(function(data){ + $('.loading', this.element).hide(); + this.prepareData(data.days, data.commits); + this.buildGraph(); + }, this) + }); + }; + + BranchGraph.prototype.prepareData = function(days, commits){ + this.days = days; + this.dayCount = days.length; + this.commits = commits; + this.commitCount = commits.length; + + this.collectParents(); + + this.mtime += 4; + this.mspace += 10; + for (var i = 0; i < this.commitCount; i++) { + if (this.commits[i].id in this.parents) { + this.commits[i].isParent = true; } - mtime = Math.max(mtime, commits[i].time); - mspace = Math.max(mspace, commits[i].space); - } - mtime = mtime + 4; - mspace = mspace + 10; - for (i = 0; i < ii; i++) { - if (commits[i].id in parents) { - commits[i].isParent = true; + this.preparedCommits[this.commits[i].id] = this.commits[i]; + } + this.collectColors(); + }; + + BranchGraph.prototype.collectParents = function(){ + for (var i = 0; i < this.commitCount; i++) { + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { + this.parents[this.commits[i].parents[j][0]] = true; } - comms[commits[i].id] = commits[i]; - } - for (var k = 0; k < mspace; k++) { - colors.push(Raphael.getColor()); - } -} - -function branchGraph(holder) { - var ch = mspace * 20 + 20, cw = mtime * 20 + 20, - r = Raphael("holder", cw, ch), - top = r.set(); - var cuday = 0, cumonth = ""; - r.rect(0, 0, days.length * 20 + 80, 30).attr({fill: "#222"}); - r.rect(0, 30, days.length * 20 + 80, 20).attr({fill: "#444"}); - - for (mm = 0; mm < days.length; mm++) { - if(days[mm] != null){ - if(cuday != days[mm][0]){ - r.text(10 + mm * 20, 40, days[mm][0]).attr({font: "14px Fontin-Sans, Arial", fill: "#DDD"}); - cuday = days[mm][0] - } - if(cumonth != days[mm][1]){ - r.text(10 + mm * 20, 15, days[mm][1]).attr({font: "14px Fontin-Sans, Arial", fill: "#EEE"}); - cumonth = days[mm][1] - } + this.mtime = Math.max(this.mtime, this.commits[i].time); + this.mspace = Math.max(this.mspace, this.commits[i].space); + } + }; + + BranchGraph.prototype.collectColors = function(){ + for (var k = 0; k < this.mspace; k++) { + this.colors.push(Raphael.getColor()); + } + }; + BranchGraph.prototype.buildGraph = function(){ + var graphWidth = $(this.element).width() + , ch = this.mspace * 20 + 20 + , cw = Math.max(graphWidth, this.mtime * 20 + 20) + , r = Raphael(this.element.get(0), cw, ch) + , top = r.set() + , cuday = 0 + , cumonth = "" + , offsetX = 20 + , offsetY = 60 + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 80); + + this.raphael = r; + + r.rect(0, 0, barWidth, 20).attr({fill: "#222"}); + r.rect(0, 20, barWidth, 20).attr({fill: "#444"}); + + for (mm = 0; mm < this.dayCount; mm++) { + if(this.days[mm] != null){ + if(cuday != this.days[mm][0]){ + // Dates + r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({ + font: "12px Monaco, Arial", + fill: "#DDD" + }); + cuday = this.days[mm][0]; + } + if(cumonth != this.days[mm][1]){ + // Months + r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({ + font: "12px Monaco, Arial", + fill: "#EEE" + }); + cumonth = this.days[mm][1]; } + } } - for (i = 0; i < ii; i++) { - var x = 10 + 20 * commits[i].time, - y = 70 + 20 * commits[i].space; - r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: "none"}); - if (commits[i].refs != null && commits[i].refs != "") { - var longrefs = commits[i].refs - var shortrefs = commits[i].refs; - if (shortrefs.length > 15){ - shortrefs = shortrefs.substr(0,13) + "..."; - } - var t = r.text(x+5, y+5, shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666", - title: longrefs, cursor: "pointer", rotation: "90"}); - - var textbox = t.getBBox(); - t.translate(textbox.height/-4,textbox.width/2); + + for (i = 0; i < this.commitCount; i++) { + var x = offsetX + 20 * this.commits[i].time + , y = offsetY + 20 * this.commits[i].space; + r.circle(x, y, 3).attr({ + fill: this.colors[this.commits[i].space], + stroke: "none" + }); + if (this.commits[i].refs != null && this.commits[i].refs != "") { + var longrefs = this.commits[i].refs + , shortrefs = this.commits[i].refs; + if (shortrefs.length > 15){ + shortrefs = shortrefs.substr(0,13) + "..."; } - for (var j = 0, jj = commits[i].parents.length; j < jj; j++) { - var c = comms[commits[i].parents[j][0]]; - if (c) { - var cx = 10 + 20 * c.time, - cy = 70 + 20 * c.space; - if (c.space == commits[i].space) { - r.path("M" + (x - 5) + "," + (y + .0001) + "L" + (15 + 20 * c.time) + "," + (y + .0001)) - .attr({stroke: colors[c.space], "stroke-width": 2}); + var t = r.text(x+5, y+8, shortrefs).attr({ + font: "12px Monaco, Arial", + fill: "#666", + title: longrefs, + cursor: "pointer", + rotation: "90" + }); - } else if (c.space < commits[i].space) { - r.path(["M", x - 5, y + .0001, "l-5-2,0,4,5,-2C", x - 5, y, x - 17, y + 2, x - 20, y - 5, "L", cx, y - 5, cx, cy]) - .attr({stroke: colors[commits[i].space], "stroke-width": 2}); - } else { - r.path(["M", x - 3, y + 6, "l-4,3,4,2,0,-5L", x - 10, y + 20, "L", x - 10, cy, cx, cy]) - .attr({stroke: colors[c.space], "stroke-width": 2}); - } - } + var textbox = t.getBBox(); + t.translate(textbox.height/-4, textbox.width/2); + } + var c; + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) { + c = this.preparedCommits[this.commits[i].parents[j][0]]; + if (c) { + var cx = offsetX + 20 * c.time + , cy = offsetY + 20 * c.space; + if (c.space == this.commits[i].space) { + r.path([ + "M", x, y, + "L", x - 20 * (c.time + 1), y + ]).attr({ + stroke: this.colors[c.space], + "stroke-width": 2 + }); + + } else if (c.space < this.commits[i].space) { + r.path(["M", x - 5, y + .0001, "l-5-2,0,4,5,-2C", x - 5, y, x - 17, y + 2, x - 20, y - 5, "L", cx, y - 5, cx, cy]) + .attr({ + stroke: this.colors[this.commits[i].space], + "stroke-width": 2 + }); + } else { + r.path(["M", x - 3, y + 6, "l-4,3,4,2,0,-5L", x - 10, y + 20, "L", x - 10, cy, cx, cy]) + .attr({ + stroke: this.colors[c.space], + "stroke-width": 2 + }); + } } - (function (c, x, y) { - top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0, cursor: "pointer"}) - .click(function(){ - location.href = location.href.replace("graph", "commits/" + c.id); - }) - .hover(function () { - var s = r.text(100, 100,c.author + "\n \n" +c.id + "\n \n" + c.message).attr({fill: "#fff"}); - this.popup = r.popupit(x, y + 5, s, 0); - top.push(this.popup.insertBefore(this)); - }, function () { - this.popup && this.popup.remove() && delete this.popup; - })); - }(commits[i], x, y)); + } + this.appendAnchor(top, this.commits[i], x, y); } top.toFront(); - var hw = holder.offsetWidth, - hh = holder.offsetHeight, - v = r.rect(hw - 8, 0, 4, Math.pow(hh, 2) / ch, 2).attr({fill: "#000", opacity: 0}), - h = r.rect(0, hh - 8, Math.pow(hw, 2) / cw, 4, 2).attr({fill: "#000", opacity: 0}), - bars = r.set(v, h), - drag, - dragger = function (e) { - if (drag) { - e = e || window.event; - holder.scrollLeft = drag.sl - (e.clientX - drag.x); - holder.scrollTop = drag.st - (e.clientY - drag.y); - } - }; - holder.onmousedown = function (e) { - e = e || window.event; - drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft}; - document.onmousemove = dragger; - bars.animate({opacity: .5}, 300); - }; - document.onmouseup = function () { - drag = false; - document.onmousemove = null; - bars.animate({opacity: 0}, 300); + this.element.scrollLeft(cw); + this.bindEvents(); + }; + + BranchGraph.prototype.bindEvents = function(){ + var drag = {} + , element = this.element; + + var dragger = function(event){ + element.scrollLeft(drag.sl - (event.clientX - drag.x)); + element.scrollTop(drag.st - (event.clientY - drag.y)); }; - holder.scrollLeft = cw; -}; -Raphael.fn.popupit = function (x, y, set, dir, size) { - dir = dir == null ? 2 : dir; - size = size || 5; - x = Math.round(x); - y = Math.round(y); - var bb = set.getBBox(), - w = Math.round(bb.width / 2), - h = Math.round(bb.height / 2), - dx = [0, w + size * 2, 0, -w - size * 2], - dy = [-h * 2 - size * 3, -h - size, 0, -h - size], - p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size, - "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size, - "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size, - "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size, - "l", -mmax(w - size, 0), 0, "z"].join(","), - xy = [{x: x, y: y + size * 2 + h}, {x: x - size * 2 - w, y: y}, {x: x, y: y - size * 2 - h}, {x: x + size * 2 + w, y: y}][dir]; - set.translate(xy.x - w - bb.x, xy.y - h - bb.y); - return this.set(this.path(p).attr({fill: "#234", stroke: "none"}).insertBefore(set.node ? set : set[0]), set); -}; -Raphael.fn.popup = function (x, y, text, dir, size) { - dir = dir == null ? 2 : dir > 3 ? 3 : dir; - size = size || 5; - text = text || "$9.99"; - var res = this.set(), - d = 3; - res.push(this.path().attr({fill: "#000", stroke: "#000"})); - res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"})); - res.update = function (X, Y, withAnimation) { - X = X || x; - Y = Y || y; - var bb = this[1].getBBox(), - w = bb.width / 2, - h = bb.height / 2, - dx = [0, w + size * 2, 0, -w - size * 2], - dy = [-h * 2 - size * 3, -h - size, 0, -h - size], - p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size, - "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size, - "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size, - "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size, - "l", -mmax(w - size, 0), 0, "z"].join(","), - xy = [{x: X, y: Y + size * 2 + h}, {x: X - size * 2 - w, y: Y}, {x: X, y: Y - size * 2 - h}, {x: X + size * 2 + w, y: Y}][dir]; - xy.path = p; - if (withAnimation) { - this.animate(xy, 500, ">"); - } else { - this.attr(xy); + + element.on({ + mousedown: function (event) { + drag = { + x: event.clientX, + y: event.clientY, + st: element.scrollTop(), + sl: element.scrollLeft() + }; + $(window).on('mousemove', dragger); + } + }); + $(window).on({ + mouseup: function(){ + //bars.animate({opacity: 0}, 300); + $(window).off('mousemove', dragger); + }, + keydown: function(event){ + if(event.keyCode == 37){ + // left + element.scrollLeft( element.scrollLeft() - 50); } - return this; - }; - return res.update(x, y); + if(event.keyCode == 38){ + // top + element.scrollTop( element.scrollTop() - 50); + } + if(event.keyCode == 39){ + // right + element.scrollLeft( element.scrollLeft() + 50); + } + if(event.keyCode == 40){ + // bottom + element.scrollTop( element.scrollTop() + 50); + } + } + }); + }; + + BranchGraph.prototype.appendAnchor = function(top, c, x, y) { + var r = this.raphael + , options = this.options + , anchor; + anchor = r.circle(x, y, 10).attr({ + fill: "#000", + opacity: 0, + cursor: "pointer" + }) + .click(function(){ + window.location = options.commit_url.replace('%s', c.id); + }) + .hover(function(){ + var text = r.text(100, 100, c.author + "\n \n" + c.id + "\n \n" + c.message).attr({ + fill: "#fff" + }); + this.popup = r.tooltip(x, y + 5, text, 0); + top.push(this.popup.insertBefore(this)); + }, function(){ + this.popup && this.popup.remove() && delete this.popup; + }); + top.push(anchor); + }; + + this.BranchGraph = BranchGraph; + +}(this); +Raphael.fn.tooltip = function (x, y, set, dir, size) { + dir = dir == null ? 2 : dir; + size = size || 5; + x = Math.round(x); + y = Math.round(y); + var mmax = Math.max + , bb = set.getBBox() + , w = Math.round(bb.width / 2) + , h = Math.round(bb.height / 2) + , dx = [0, w + size * 2, 0, -w - size * 2] + , dy = [-h * 2 - size * 3, -h - size, 0, -h - size] + , p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size, + "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size, + "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size, + "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size, + "l", -mmax(w - size, 0), 0, "z"].join(",") + , xy = [{x: x, y: y + size * 2 + h}, {x: x - size * 2 - w, y: y}, {x: x, y: y - size * 2 - h}, {x: x + size * 2 + w, y: y}][dir]; + set.translate(xy.x - w - bb.x, xy.y - h - bb.y); + return this.set(this.path(p).attr({fill: "#234", stroke: "none"}).insertBefore(set.node ? set : set[0]), set); }; diff --git a/vendor/assets/stylesheets/jquery.ui.aristo.css b/vendor/assets/stylesheets/jquery.ui.aristo.css deleted file mode 100644 index 8cc6e7877308c6f2c9106092baf32b2ec810b581..0000000000000000000000000000000000000000 --- a/vendor/assets/stylesheets/jquery.ui.aristo.css +++ /dev/null @@ -1,738 +0,0 @@ -/* - * jQuery UI CSS Framework 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - */ - -/* Layout helpers -----------------------------------*/ -.ui-helper-hidden { display: none; } -.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } -.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } -.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } -.ui-helper-clearfix { display: inline-block; } -/* required comment for clearfix to work in Opera \*/ -* html .ui-helper-clearfix { height:1%; } -.ui-helper-clearfix { display:block; } -/* end clearfix */ -.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } - - -/* Interaction Cues -----------------------------------*/ -.ui-state-disabled { cursor: default !important; } - - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } - - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } - - -/* - * jQuery UI CSS Framework 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - * - * To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller - */ - - -/* Component containers -----------------------------------*/ -.ui-widget { font-family: Arial,sans-serif; font-size: 1.1em; } -.ui-widget .ui-widget { font-size: 1em; } -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; } -.ui-widget-content { border: 1px solid #B6B6B6; background: #ffffff; color: #4F4F4F; } -.ui-widget-content a { color: #4F4F4F; } -.ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; } -.ui-widget-header { - background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */ - background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */ - background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */ -} -.ui-widget-header a { color: #4F4F4F; } - -/* Interaction states -----------------------------------*/ -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #B6B6B6; font-weight: normal; color: #4F4F4F; } -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { - background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */ - background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */ - background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */ - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; - -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; - box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset; -} -.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; } -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #9D9D9D; font-weight: normal; color: #313131; } -.ui-state-hover a, .ui-state-hover a:hover { color: #313131; text-decoration: none; } -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { - outline: none; - color: #1c4257; border: 1px solid #7096ab; - background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */ - background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */ - background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */ - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; } -.ui-widget :active { outline: none; } - -/* Interaction Cues -----------------------------------*/ -.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { border: 1px solid #d2dbf4; background: #f4f8fd; color: #0d2054; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; } -.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } -.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { border: 1px solid #e2d0d0; background: #fcf0f0; color: #280b0b; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; } -.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } -.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } -.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } -.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } -.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { width: 16px; height: 16px; background-image: url(ui-icons_222222_256x240.png); } -.ui-widget-content .ui-icon {background-image: url(ui-icons_222222_256x240.png); } -.ui-widget-header .ui-icon {background-image: url(ui-icons_222222_256x240.png); } -.ui-state-default .ui-icon { background-image: url(ui-icons_454545_256x240.png); } -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(ui-icons_454545_256x240.png); } -.ui-state-active .ui-icon {background-image: url(ui-icons_454545_256x240.png); } -.ui-state-highlight .ui-icon {background-image: url(ui-icons_454545_256x240.png); } -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { background: url(icon_sprite.png) -16px 0 no-repeat !important; } -.ui-state-highlight .ui-icon, .ui-state-error .ui-icon { margin-top: -1px; } - -/* positioning */ -.ui-icon-carat-1-n { background-position: 0 0; } -.ui-icon-carat-1-ne { background-position: -16px 0; } -.ui-icon-carat-1-e { background-position: -32px 0; } -.ui-icon-carat-1-se { background-position: -48px 0; } -.ui-icon-carat-1-s { background-position: -64px 0; } -.ui-icon-carat-1-sw { background-position: -80px 0; } -.ui-icon-carat-1-w { background-position: -96px 0; } -.ui-icon-carat-1-nw { background-position: -112px 0; } -.ui-icon-carat-2-n-s { background-position: -128px 0; } -.ui-icon-carat-2-e-w { background-position: -144px 0; } -.ui-icon-triangle-1-n { background-position: 0 -16px; } -.ui-icon-triangle-1-ne { background-position: -16px -16px; } -.ui-icon-triangle-1-e { background-position: -32px -16px; } -.ui-icon-triangle-1-se { background-position: -48px -16px; } -.ui-icon-triangle-1-s { background-position: -64px -16px; } -.ui-icon-triangle-1-sw { background-position: -80px -16px; } -.ui-icon-triangle-1-w { background-position: -96px -16px; } -.ui-icon-triangle-1-nw { background-position: -112px -16px; } -.ui-icon-triangle-2-n-s { background-position: -128px -16px; } -.ui-icon-triangle-2-e-w { background-position: -144px -16px; } -.ui-icon-arrow-1-n { background-position: 0 -32px; } -.ui-icon-arrow-1-ne { background-position: -16px -32px; } -.ui-icon-arrow-1-e { background-position: -32px -32px; } -.ui-icon-arrow-1-se { background-position: -48px -32px; } -.ui-icon-arrow-1-s { background-position: -64px -32px; } -.ui-icon-arrow-1-sw { background-position: -80px -32px; } -.ui-icon-arrow-1-w { background-position: -96px -32px; } -.ui-icon-arrow-1-nw { background-position: -112px -32px; } -.ui-icon-arrow-2-n-s { background-position: -128px -32px; } -.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } -.ui-icon-arrow-2-e-w { background-position: -160px -32px; } -.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } -.ui-icon-arrowstop-1-n { background-position: -192px -32px; } -.ui-icon-arrowstop-1-e { background-position: -208px -32px; } -.ui-icon-arrowstop-1-s { background-position: -224px -32px; } -.ui-icon-arrowstop-1-w { background-position: -240px -32px; } -.ui-icon-arrowthick-1-n { background-position: 0 -48px; } -.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } -.ui-icon-arrowthick-1-e { background-position: -32px -48px; } -.ui-icon-arrowthick-1-se { background-position: -48px -48px; } -.ui-icon-arrowthick-1-s { background-position: -64px -48px; } -.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } -.ui-icon-arrowthick-1-w { background-position: -96px -48px; } -.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } -.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } -.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } -.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } -.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } -.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } -.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } -.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } -.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } -.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } -.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } -.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } -.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } -.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } -.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } -.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } -.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } -.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } -.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } -.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } -.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } -.ui-icon-arrow-4 { background-position: 0 -80px; } -.ui-icon-arrow-4-diag { background-position: -16px -80px; } -.ui-icon-extlink { background-position: -32px -80px; } -.ui-icon-newwin { background-position: -48px -80px; } -.ui-icon-refresh { background-position: -64px -80px; } -.ui-icon-shuffle { background-position: -80px -80px; } -.ui-icon-transfer-e-w { background-position: -96px -80px; } -.ui-icon-transferthick-e-w { background-position: -112px -80px; } -.ui-icon-folder-collapsed { background-position: 0 -96px; } -.ui-icon-folder-open { background-position: -16px -96px; } -.ui-icon-document { background-position: -32px -96px; } -.ui-icon-document-b { background-position: -48px -96px; } -.ui-icon-note { background-position: -64px -96px; } -.ui-icon-mail-closed { background-position: -80px -96px; } -.ui-icon-mail-open { background-position: -96px -96px; } -.ui-icon-suitcase { background-position: -112px -96px; } -.ui-icon-comment { background-position: -128px -96px; } -.ui-icon-person { background-position: -144px -96px; } -.ui-icon-print { background-position: -160px -96px; } -.ui-icon-trash { background-position: -176px -96px; } -.ui-icon-locked { background-position: -192px -96px; } -.ui-icon-unlocked { background-position: -208px -96px; } -.ui-icon-bookmark { background-position: -224px -96px; } -.ui-icon-tag { background-position: -240px -96px; } -.ui-icon-home { background-position: 0 -112px; } -.ui-icon-flag { background-position: -16px -112px; } -.ui-icon-calendar { background-position: -32px -112px; } -.ui-icon-cart { background-position: -48px -112px; } -.ui-icon-pencil { background-position: -64px -112px; } -.ui-icon-clock { background-position: -80px -112px; } -.ui-icon-disk { background-position: -96px -112px; } -.ui-icon-calculator { background-position: -112px -112px; } -.ui-icon-zoomin { background-position: -128px -112px; } -.ui-icon-zoomout { background-position: -144px -112px; } -.ui-icon-search { background-position: -160px -112px; } -.ui-icon-wrench { background-position: -176px -112px; } -.ui-icon-gear { background-position: -192px -112px; } -.ui-icon-heart { background-position: -208px -112px; } -.ui-icon-star { background-position: -224px -112px; } -.ui-icon-link { background-position: -240px -112px; } -.ui-icon-cancel { background-position: 0 -128px; } -.ui-icon-plus { background-position: -16px -128px; } -.ui-icon-plusthick { background-position: -32px -128px; } -.ui-icon-minus { background-position: -48px -128px; } -.ui-icon-minusthick { background-position: -64px -128px; } -.ui-icon-close { background-position: -80px -128px; } -.ui-icon-closethick { background-position: -96px -128px; } -.ui-icon-key { background-position: -112px -128px; } -.ui-icon-lightbulb { background-position: -128px -128px; } -.ui-icon-scissors { background-position: -144px -128px; } -.ui-icon-clipboard { background-position: -160px -128px; } -.ui-icon-copy { background-position: -176px -128px; } -.ui-icon-contact { background-position: -192px -128px; } -.ui-icon-image { background-position: -208px -128px; } -.ui-icon-video { background-position: -224px -128px; } -.ui-icon-script { background-position: -240px -128px; } -.ui-icon-alert { background-position: 0 -144px; } -.ui-icon-info { background: url(icon_sprite.png) 0 0 no-repeat !important; } -.ui-icon-notice { background-position: -32px -144px; } -.ui-icon-help { background-position: -48px -144px; } -.ui-icon-check { background-position: -64px -144px; } -.ui-icon-bullet { background-position: -80px -144px; } -.ui-icon-radio-off { background-position: -96px -144px; } -.ui-icon-radio-on { background-position: -112px -144px; } -.ui-icon-pin-w { background-position: -128px -144px; } -.ui-icon-pin-s { background-position: -144px -144px; } -.ui-icon-play { background-position: 0 -160px; } -.ui-icon-pause { background-position: -16px -160px; } -.ui-icon-seek-next { background-position: -32px -160px; } -.ui-icon-seek-prev { background-position: -48px -160px; } -.ui-icon-seek-end { background-position: -64px -160px; } -.ui-icon-seek-start { background-position: -80px -160px; } -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ -.ui-icon-seek-first { background-position: -80px -160px; } -.ui-icon-stop { background-position: -96px -160px; } -.ui-icon-eject { background-position: -112px -160px; } -.ui-icon-volume-off { background-position: -128px -160px; } -.ui-icon-volume-on { background-position: -144px -160px; } -.ui-icon-power { background-position: 0 -176px; } -.ui-icon-signal-diag { background-position: -16px -176px; } -.ui-icon-signal { background-position: -32px -176px; } -.ui-icon-battery-0 { background-position: -48px -176px; } -.ui-icon-battery-1 { background-position: -64px -176px; } -.ui-icon-battery-2 { background-position: -80px -176px; } -.ui-icon-battery-3 { background-position: -96px -176px; } -.ui-icon-circle-plus { background-position: 0 -192px; } -.ui-icon-circle-minus { background-position: -16px -192px; } -.ui-icon-circle-close { background-position: -32px -192px; } -.ui-icon-circle-triangle-e { background-position: -48px -192px; } -.ui-icon-circle-triangle-s { background-position: -64px -192px; } -.ui-icon-circle-triangle-w { background-position: -80px -192px; } -.ui-icon-circle-triangle-n { background-position: -96px -192px; } -.ui-icon-circle-arrow-e { background-position: -112px -192px; } -.ui-icon-circle-arrow-s { background-position: -128px -192px; } -.ui-icon-circle-arrow-w { background-position: -144px -192px; } -.ui-icon-circle-arrow-n { background-position: -160px -192px; } -.ui-icon-circle-zoomin { background-position: -176px -192px; } -.ui-icon-circle-zoomout { background-position: -192px -192px; } -.ui-icon-circle-check { background-position: -208px -192px; } -.ui-icon-circlesmall-plus { background-position: 0 -208px; } -.ui-icon-circlesmall-minus { background-position: -16px -208px; } -.ui-icon-circlesmall-close { background-position: -32px -208px; } -.ui-icon-squaresmall-plus { background-position: -48px -208px; } -.ui-icon-squaresmall-minus { background-position: -64px -208px; } -.ui-icon-squaresmall-close { background-position: -80px -208px; } -.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } -.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } -.ui-icon-grip-solid-vertical { background-position: -32px -224px; } -.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } -.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } -.ui-icon-grip-diagonal-se { background-position: -80px -224px; } - - -/* Misc visuals -----------------------------------*/ - -/* Corner radius */ -.ui-corner-tl { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; } -.ui-corner-tr { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; } -.ui-corner-bl { -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; } -.ui-corner-br { -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } -.ui-corner-top { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; } -.ui-corner-bottom { -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } -.ui-corner-right { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; } -.ui-corner-left { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; } -.ui-corner-all { -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; } - -/* Overlays */ -.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); } -.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #000000; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* - * jQuery UI Resizable 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Resizable#theming - */ -.ui-resizable { position: relative;} -.ui-resizable-handle { position: absolute; font-size: 0.1px; z-index: 999; display: block;} -.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } -.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } -.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } -.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } -.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } -.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } -.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } -.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } -.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* - * jQuery UI Selectable 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Selectable#theming - */ -.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } -/* - * jQuery UI Accordion 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Accordion#theming - */ -/* IE/Win - Fix animation bug - #4615 */ -.ui-accordion { width: 100%; } -.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } -.ui-accordion .ui-accordion-header, .ui-accordion .ui-accordion-content { -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; } -.ui-accordion .ui-accordion-li-fix { display: inline; } -.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } -.ui-accordion .ui-accordion-header a { display: block; font-size: 12px; font-weight: bold; padding: .5em .5em .5em .7em; } -.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } -.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } -.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } -.ui-accordion .ui-accordion-content-active { display: block; }/* - * jQuery UI Autocomplete 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete#theming - */ -.ui-autocomplete { - position: absolute; cursor: default; z-index: 3; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3); - -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3); - box-shadow: 0 1px 5px rgba(0,0,0,0.3); -} - -/* workarounds */ -* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ - -/* - * jQuery UI Menu 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Menu#theming - */ -.ui-menu { - list-style:none; - padding: 2px; - margin: 0; - display:block; - float: left; -} -.ui-menu .ui-menu { - margin-top: -3px; -} -.ui-menu .ui-menu-item { - margin:0; - padding: 0; - zoom: 1; - float: left; - clear: left; - width: 100%; -} -.ui-menu .ui-menu-item a { - text-decoration:none; - display:block; - padding:.2em .4em; - line-height:1.5; - zoom:1; -} -.ui-menu .ui-menu-item a.ui-state-hover, -.ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; - background: #5f83b9; - color: #FFFFFF; - text-shadow: 0px 1px 1px #234386; - border-color: #466086; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} -/* - * jQuery UI Button 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Button#theming - */ -.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; -webkit-user-select: none; -moz-user-select: none; user-select: none; } /* the overflow property removes extra width in IE */ -.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ -button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ -.ui-button-icons-only { width: 3.4em; } -button.ui-button-icons-only { width: 3.7em; } - -/* button animation properties */ -.ui-button { - -webkit-transition: all 250ms ease-in-out; - -moz-transition: all 250ms ease-in-out; - -o-transition: all 250ms ease-in-out; - transition: all 250ms ease-in-out; -} - -/*states*/ -.ui-button.ui-state-hover { - -moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; - -webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; -} -.ui-button.ui-state-focus { - outline: none; - color: #1c4257; - border-color: #7096ab; - background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */ - background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */ - background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */ - -moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; - -webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; -} - -/*button text element */ -.ui-button .ui-button-text { display: block; line-height: 1.4; font-size: 14px; font-weight: bold; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); } -.ui-button-text-only .ui-button-text { padding: .4em 1em; } -.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } -.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } -.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } -.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } -/* no icon support for input elements, provide padding by default */ -input.ui-button, .ui-widget-content input.ui-button { font-size: 14px; font-weight: bold; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); padding: 0 1em !important; height: 33px; } -/*remove submit button internal padding in Firefox*/ -input.ui-button::-moz-focus-inner { - border: 0; - padding: 0; -} -/* fix webkits handling of the box model */ -@media screen and (-webkit-min-device-pixel-ratio:0) { - input.ui-button { - height: 31px !important; - } -} - - -/*button icon element(s) */ -.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } -.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } -.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } -.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } -.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } - -/*button sets*/ -.ui-buttonset { margin-right: 7px; } -.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } -.ui-buttonset .ui-button.ui-state-active { color: #1c4257; border-color: #7096ab; } -.ui-buttonset .ui-button.ui-state-active { - background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */ - background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */ - background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */ - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -/* workarounds */ -button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ -/* - * jQuery UI Dialog 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Dialog#theming - */ -.ui-dialog { position: absolute; padding: 0; width: 300px; overflow: hidden; } -.ui-dialog { - -webkit-box-shadow: 0 2px 12px rgba(0,0,0,0.6); - -moz-box-shadow: 0 2px 12px rgba(0,0,0,0.6); - box-shadow: 0 2px 12px rgba(0,0,0,0.6); -} -.ui-dialog .ui-dialog-titlebar { padding: 0.7em 1em 0.6em 1em; position: relative; border: none; border-bottom: 1px solid #979797; -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; } -.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; font-size: 14px; text-shadow: 0 1px 0 rgba(255,255,255,0.5); } -.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .8em; top: 55%; width: 16px; margin: -10px 0 0 0; padding: 0; height: 16px; } -.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; background: url(icon_sprite.png) 0 -16px no-repeat; } -.ui-dialog .ui-dialog-titlebar-close:hover span { background-position: -16px -16px; } -.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; border: 0; } -.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } -.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } -.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } -.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } -.ui-draggable .ui-dialog-titlebar { cursor: move; } -/* - * jQuery UI Slider 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Slider#theming - */ -.ui-slider { position: relative; text-align: left; background: #d7d7d7; z-index: 1; } -.ui-slider { -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; } -.ui-slider .ui-slider-handle { background: url(slider_handles.png) 0px -23px no-repeat; position: absolute; z-index: 2; width: 23px; height: 23px; cursor: default; border: none; outline: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } -.ui-slider .ui-state-hover, .ui-slider .ui-state-active { background-position: 0 0; } -.ui-slider .ui-slider-range { background: #a3cae0; position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } -.ui-slider .ui-slider-range { -moz-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; -webkit-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; } - - -.ui-slider-horizontal { height: 5px; } -.ui-slider-horizontal .ui-slider-handle { top: -8px; margin-left: -13px; } -.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } -.ui-slider-horizontal .ui-slider-range-min { left: 0; } -.ui-slider-horizontal .ui-slider-range-max { right: 0; } - -.ui-slider-vertical { width: 5px; height: 100px; } -.ui-slider-vertical .ui-slider-handle { left: -8px; margin-left: 0; margin-bottom: -13px; } -.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } -.ui-slider-vertical .ui-slider-range-min { bottom: 0; } -.ui-slider-vertical .ui-slider-range-max { top: 0; }/* - * jQuery UI Tabs 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs#theming - */ -.ui-tabs { position: relative; zoom: 1; border: 0; background: transparent; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ -.ui-tabs .ui-tabs-nav { margin: 0; padding: 0; background: transparent; border-width: 0 0 1px 0; } -.ui-tabs .ui-tabs-nav { - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} -.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } -.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; font-size: 12px; font-weight: bold; text-shadow: 0 1px 0 rgba(255,255,255,0.5); } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; background: #fff; border-color: #B6B6B6; } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; outline: none; } -.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ -.ui-tabs .ui-tabs-panel { display: block; border-width: 0 1px 1px 1px; padding: 1em 1.4em; background: none; } -.ui-tabs .ui-tabs-panel { background: #FFF; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; -} -.ui-tabs .ui-tabs-hide { display: none !important; } -/* - * jQuery UI Datepicker 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Datepicker#theming - */ -.ui-datepicker { width: 17em; padding: 0; display: none; border-color: #DDDDDD; } -.ui-datepicker { - -moz-box-shadow: 0 4px 8px rgba(0,0,0,0.5); - -webkit-box-shadow: 0 4px 8px rgba(0,0,0,0.5); - box-shadow: 0 4px 8px rgba(0,0,0,0.5); -} -.ui-datepicker .ui-datepicker-header { position:relative; padding:.35em 0; border: none; border-bottom: 1px solid #B6B6B6; -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; } -.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 6px; width: 1.8em; height: 1.8em; } -.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { border: 1px none; } -.ui-datepicker .ui-datepicker-prev { left:2px; } -.ui-datepicker .ui-datepicker-next { right:2px; } -.ui-datepicker .ui-datepicker-prev span { background-position: 0px -32px !important; } -.ui-datepicker .ui-datepicker-next span { background-position: -16px -32px !important; } -.ui-datepicker .ui-datepicker-prev-hover span { background-position: 0px -48px !important; } -.ui-datepicker .ui-datepicker-next-hover span { background-position: -16px -48px !important; } -.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; background: url(icon_sprite.png) no-repeat; } -.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; font-size: 12px; text-shadow: 0 1px 0 rgba(255,255,255,0.6); } -.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } -.ui-datepicker select.ui-datepicker-month-year {width: 100%;} -.ui-datepicker select.ui-datepicker-month, -.ui-datepicker select.ui-datepicker-year { width: 49%;} -.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } -.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } -.ui-datepicker td { border: 0; padding: 1px; } -.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } -.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } -.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } -.ui-datepicker table .ui-state-highlight { border-color: #5F83B9; } -.ui-datepicker table .ui-state-hover { background: #5F83B9; color: #FFF; font-weight: bold; text-shadow: 0 1px 1px #234386; -webkit-box-shadow: 0 0px 0 rgba(255,255,255,0.6) inset; -moz-box-shadow: 0 0px 0 rgba(255,255,255,0.6) inset; box-shadow: 0 0px 0 rgba(255,255,255,0.6) inset; border-color: #5F83B9; } -.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; } -.ui-datepicker-calendar .ui-state-active { background: #5F83B9; border-color: #5F83B9; color: #FFF; font-weight: bold; text-shadow: 0 1px 1px #234386; } - -/* with multiple calendars */ -.ui-datepicker.ui-datepicker-multi { width:auto; } -.ui-datepicker-multi .ui-datepicker-group { float:left; } -.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } -.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } -.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } -.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } -.ui-datepicker-row-break { clear:both; width:100%; } - -/* RTL support */ -.ui-datepicker-rtl { direction: rtl; } -.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } -.ui-datepicker-rtl .ui-datepicker-group { float:right; } -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } -.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } - -/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ -.ui-datepicker-cover { - display: none; /*sorry for IE5*/ - display/**/: block; /*sorry for IE5*/ - position: absolute; /*must have*/ - z-index: -1; /*must have*/ - filter: mask(); /*must have*/ - top: -4px; /*must have*/ - left: -4px; /*must have*/ - width: 200px; /*must have*/ - height: 200px; /*must have*/ -}/* - * jQuery UI Progressbar 1.8.7 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar#theming - */ -.ui-progressbar { height: 12px; text-align: left; background: #FFF url(progress_bar.gif) 0 -14px repeat-x; } -.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; background: url(progress_bar.gif) 0 0 repeat-x; } - -/* Extra Input Field Styling */ -.ui-form textarea, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]) { - padding: 3px; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - border-radius: 2px; - border: 1px solid #cecece; - outline: none; - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2); - -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2); - box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2); - -webkit-transition: all 250ms ease-in-out; - -moz-transition: all 250ms ease-in-out; - -o-transition: all 250ms ease-in-out; - transition: all 250ms ease-in-out; -} -.ui-form textarea:hover, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):hover { - border: 1px solid #bdbdbd; - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2); - -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2); - box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2); -} -.ui-form textarea:focus, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):focus { - border: 1px solid #95bdd4; - -webkit-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2); - -moz-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2); - box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2); -}