diff --git a/.gitignore b/.gitignore
index 084edd30df..d151849b71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,6 @@ doc/code/*
.secret
*.log
public/uploads.*
+public/assets/
+.envrc
+dump.rdb
diff --git a/.pkgr.yml b/.pkgr.yml
new file mode 100644
index 0000000000..09cb83783d
--- /dev/null
+++ b/.pkgr.yml
@@ -0,0 +1,19 @@
+user: git
+group: git
+before_precompile: ./bin/pkgr_before_precompile.sh
+targets:
+ debian-7: &wheezy
+ build_dependencies:
+ - libicu-dev
+ dependencies:
+ - libicu48
+ - libpcre3
+ - git
+ ubuntu-12.04: *wheezy
+ ubuntu-14.04:
+ build_dependencies:
+ - libicu-dev
+ dependencies:
+ - libicu52
+ - libpcre3
+ - git
diff --git a/.rspec b/.rspec
index 7488cbe779..4e1e0d2f72 100644
--- a/.rspec
+++ b/.rspec
@@ -1 +1 @@
---color --drb
+--color
diff --git a/.travis.yml b/.travis.yml
index c137aec907..9bab337f33 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,25 +1,32 @@
language: ruby
env:
global:
- - DB=mysql
- TRAVIS=true
matrix:
- - TASK=spinach
- - TASK=spec
- - TASK=jasmine:ci
+ - TASK=spinach DB=mysql
+ - TASK=spec:api DB=mysql
+ - TASK=spec:feature DB=mysql
+ - TASK=spec:other DB=mysql
+ - TASK=jasmine:ci DB=mysql
+ - TASK=spinach DB=postgresql
+ - TASK=spec:api DB=postgresql
+ - TASK=spec:feature DB=postgresql
+ - TASK=spec:other DB=postgresql
+ - TASK=jasmine:ci DB=postgresql
before_install:
- sudo apt-get install libicu-dev -y
- - gem install charlock_holmes -v="0.6.9"
branches:
only:
- 'master'
rvm:
- 2.0.0
services:
- - mysql
+ - redis-server
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:setup"
- "bundle exec rake db:seed_fu"
script: "bundle exec rake $TASK --trace"
+notifications:
+ email: false
diff --git a/CHANGELOG b/CHANGELOG
index 6994c4cf97..2f7874ba72 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,194 @@
+v 6.9.0
+ - Store Rails cache data in the Redis `cache:gitlab` namespace
+ - Adjust MySQL limits for existing installations
+ - Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed)
+ - Markdown preview or diff during editing via web editor (Evgeniy Sokovikov)
+ - Give the Rails cache its own Redis namespace
+ - Add ability to set different ssh host, if different from http/https
+ - Fix syntax highlighting for code comments blocks
+ - Improve comments loading logic
+ - Stop refreshing comments when the tab is hidden
+ - Improve issue and merge request mobile UI (Drew Blessing)
+ - Document how to convert a backup to PostgreSQL
+ - Fix locale bug in backup manager
+ - Fix can not automerge when MR description is too long
+ - Fix wiki backup skip bug
+ - Two Step MR creation process
+ - Remove unwanted files from satellite working directory with git clean -fdx
+ - Accept merge request via API (sponsored by O'Reilly Media)
+ - Add more access checks during API calls
+ - Block SSH access for 'disabled' Active Directory users
+
+v 6.8.0
+ - Ability to at mention users that are participating in issue and merge req. discussion
+ - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
+ - Make user search case-insensitive (Christopher Arnold)
+ - Remove omniauth-ldap nickname bug workaround
+ - Drop all tables before restoring a Postgres backup
+ - Make the repository downloads path configurable
+ - Create branches via API (sponsored by O'Reilly Media)
+ - Changed permission of gitlab-satellites directory not to be world accessible
+ - Protected branch does not allow force push
+ - Fix popen bug in `rake gitlab:satellites:create`
+ - Disable connection reaping for MySQL
+ - Allow oauth signup without email for twitter and github
+ - Fix faulty namespace names that caused 500 on user creation
+ - Option to disable standard login
+ - Clean old created archives from repository downloads directory
+ - Fix download link for huge MR diffs
+ - Expose event and mergerequest timestamps in API
+ - Fix emails on push service when only one commit is pushed
+
+v 6.7.3
+ - Fix the merge notification email not being sent (Pierre de La Morinerie)
+ - Drop all tables before restoring a Postgres backup
+ - Remove yanked modernizr gem
+
+v 6.7.2
+ - Fix upgrader script
+
+v 6.7.1
+ - Fix GitLab CI integration
+
+v 6.7.0
+ - Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
+ - Add support for Gemnasium as a Project Service (Olivier Gonzalez)
+ - Add edit file button to MergeRequest diff
+ - Public groups (Jason Hollingsworth)
+ - Cleaner headers in Notification Emails (Pierre de La Morinerie)
+ - Blob and tree gfm links to anchors work
+ - Piwik Integration (Sebastian Winkler)
+ - Show contribution guide link for new issue form (Jeroen van Baarsen)
+ - Fix CI status for merge requests from fork
+ - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+ - New page load indicator that includes a spinner that scrolls with the page
+ - Converted all the help sections into markdown
+ - LDAP user filters
+ - Streamline the content of notification emails (Pierre de La Morinerie)
+ - Fixes a bug with group member administration (Matt DeTullio)
+ - Sort tag names using VersionSorter (Robert Speicher)
+ - Add GFM autocompletion for MergeRequests (Robert Speicher)
+ - Add webhook when a new tag is pushed (Jeroen van Baarsen)
+ - Add button for toggling inline comments in diff view
+ - Add retry feature for repository import
+ - Reuse the GitLab LDAP connection within each request
+ - Changed markdown new line behaviour to conform to markdown standards
+ - Fix global search
+ - Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
+ - Create and Update MR calls now support the description parameter (Greg Messner)
+ - Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
+ - Added Slack service integration (Federico Ravasio)
+ - Better API responses for access_levels (sponsored by O'Reilly Media)
+ - Requires at least 2 unicorn workers
+ - Requires gitlab-shell v1.9+
+ - Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License)
+ - Fix `/:username.keys` response content type (Dmitry Medvinsky)
+
+v 6.6.5
+ - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
+ - Hide mr close button for comment form if merge request was closed or inline comment
+ - Adds ability to reopen closed merge request
+
+v 6.6.4
+ - Add missing html escape for highlighted code blocks in comments, issues
+
+v 6.6.3
+ - Fix 500 error when edit yourself from admin area
+ - Hide private groups for public profiles
+
+v 6.6.2
+ - Fix 500 error on branch/tag create or remove via UI
+
+v 6.6.1
+ - Fix 500 error on files tab if submodules presents
+
+v 6.6.0
+ - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys
+ - Permissions: Developer now can manage issue tracker (modify any issue)
+ - Improve Code Compare page performance
+ - Group avatar
+ - Pygments.rb replaced with highlight.js
+ - Improve Merge request diff store logic
+ - Improve render performnace for MR show page
+ - Fixed Assembla hardcoded project name
+ - Jira integration documentation
+ - Refactored app/services
+ - Remove snippet expiration
+ - Mobile UI improvements (Drew Blessing)
+ - Fix block/remove UI for admin::users#show page
+ - Show users' group membership on users' activity page (Robert Djurasaj)
+ - User pages are visible without login if user is authorized to a public project
+ - Markdown rendered headers have id derived from their name and link to their id
+ - Improve application to work faster with large groups (100+ members)
+ - Multiple emails per user
+ - Show last commit for file when view file source
+ - Restyle Issue#show page and MR#show page
+ - Ability to filter by multiple labels for Issues page
+ - Rails version to 4.0.3
+ - Fixed attachment identifier displaying underneath note text (Jason Blanchard)
+
+v 6.5.1
+ - Fix branch selectbox when create merge request from fork
+
+v 6.5.0
+ - Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard)
+ - Add color custimization and previewing to broadcast messages
+ - Fixed notes anchors
+ - Load new comments in issues dynamically
+ - Added sort options to Public page
+ - New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media)
+ - Add project visibility icons to dashboard
+ - Enable secure cookies if https used
+ - Protect users/confirmation with rack_attack
+ - Default HTTP headers to protect against MIME-sniffing, force https if enabled
+ - Bootstrap 3 with responsive UI
+ - New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth)
+ - Restyled accept widgets for MR
+ - SCSS refactored
+ - Use jquery timeago plugin
+ - Fix 500 error for rdoc files
+ - Ability to customize merge commit message (sponsored by Say Media)
+ - Search autocomplete via ajax
+ - Add website url to user profile
+ - Files API supports base64 encoded content (sponsored by O'Reilly Media)
+ - Added support for Go's repository retrieval (Bruno Albuquerque)
+
+v6.4.3
+ - Don't use unicorn worker killer if PhusionPassenger is defined
+
+v6.4.2
+ - Fixed wrong behaviour of script/upgrade.rb
+
+v6.4.1
+ - Fixed bug with repository rename
+ - Fixed bug with project transfer
+
+v 6.4.0
+ - Added sorting to project issues page (Jason Blanchard)
+ - Assembla integration (Carlos Paramio)
+ - Fixed another 500 error with submodules
+ - UI: More compact issues page
+ - Minimal password length increased to 8 symbols
+ - Side-by-side diff view (Steven Thonus)
+ - Internal projects (Jason Hollingsworth)
+ - Allow removal of avatar (Drew Blessing)
+ - Project web hooks now support issues and merge request events
+ - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
+ - Expire event cache on avatar creation/removal (Drew Blessing)
+ - Archiving old projects (Steven Thonus)
+ - Rails 4
+ - Add time ago tooltips to show actual date/time
+ - UI: Fixed UI for admin system hooks
+ - Ruby script for easier GitLab upgrade
+ - Do not remove Merge requests if fork project was removed
+ - Improve sign-in/signup UX
+ - Add resend confirmation link to sign-in page
+ - Set noreply@HOSTNAME for reply_to field in all emails
+ - Show GitLab API version on Admin#dashboard
+ - API Cross-origin resource sharing
+ - Show READMe link at project home page
+ - Show repo size for projects in Admin area
+
v 6.3.0
- API for adding gitlab-ci service
- Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey)
@@ -7,6 +198,36 @@ v 6.3.0
- Security improvements
- Added support for GitLab CI 4.0
- Fixed issue with 500 error when group did not exist
+ - Ability to leave project
+ - You can create file in repo using UI
+ - You can remove file from repo using UI
+ - API: dropped default_branch attribute from project during creation
+ - Project default_branch is not stored in db any more. It takes from repo now.
+ - Admin broadcast messages
+ - UI improvements
+ - Dont show last push widget if user removed this branch
+ - Fix 500 error for repos with newline in file name
+ - Extended html titles
+ - API: create/update/delete repo files
+ - Admin can transfer project to any namespace
+ - API: projects/all for admin users
+ - Fix recent branches order
+
+v 6.2.4
+ - Security: Cast API private_token to string (CVE-2013-4580)
+ - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+ - Fix for Git SSH access for LDAP users
+
+v 6.2.3
+ - Security: More protection against CVE-2013-4489
+ - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+ - Fix sidekiq rake tasks
+
+v 6.2.2
+ - Security: Update gitlab_git (CVE-2013-4489)
+
+v 6.2.1
+ - Security: Fix issue with generated passwords for new users
v 6.2.0
- Public project pages are now visible to everyone (files, issues, wik, etc.)
@@ -28,7 +249,7 @@ v 6.2.0
- Avatar upload on profile page with a maximum of 100KB (Steven Thonus)
- Store the sessions in Redis instead of the cookie store
- Fixed relative links in markdown
- - User must confirm his email if signup enabled
+ - User must confirm their email if signup enabled
- User must confirm changed email
v 6.1.0
@@ -50,7 +271,7 @@ v 6.1.0
- Add links to create branch/tag from project home page
- Add public-project? checkbox to new-project view
- Improved compare page. Added link to proceed into Merge Request
- - Send email to user when he was added to group
+ - Send an email to a user when they are added to group
- New landing page when you have 0 projects
v 6.0.0
@@ -93,6 +314,14 @@ v 6.0.0
- Improved MR comments logic
- Render readme file for projects in public area
+v 5.4.2
+ - Security: Cast API private_token to string (CVE-2013-4580)
+ - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583)
+
+v 5.4.1
+ - Security: Fixes for CVE-2013-4489
+ - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546)
+
v 5.4.0
- Ability to edit own comments
- Documentation improvements
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d1fdd93850..780db547f8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,38 +1,48 @@
# Contribute to GitLab
-This guide details how to use issues and pull requests to improve GitLab.
-
-- [Closing policy for issues and pull requests](#closing-policy-for-issues-and-pull-requests)
-- [Issue tracker](#issue-tracker)
-- [Pull requests](#pull-requests)
-- [Security vulnerabilities](#security-vulnerabilities)
+This guide details how contribute to GitLab.
If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md).
-## Closing policy for issues and pull requests
+## Contributor license agreement
-GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. Out of respect for our volunteers, issues and pull requests not in line with the guidelines listed in this document may be closed without notice.
+By submitting code as an individual you agree to the [individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md). By submitting code as an entity you agree to the [corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
+
+## Security vulnerability disclosure
+
+Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+
+## Closing policy for issues and merge requests
+
+GitLab is a popular open source project and the capacity to deal with issues and merge requests is limited. Out of respect for our volunteers, issues and merge requests not in line with the guidelines listed in this document may be closed without notice.
Please treat our volunteers with courtesy and respect, it will go a long way towards getting your issue resolved.
-Issues and pull requests should be in English and contain appropriate language for audiences of all ages.
+Issues and merge requests should be in English and contain appropriate language for audiences of all ages.
## Issue tracker
-To get support for your particular problem please use the channels as detailed in [the getting help section of the readme](https://github.com/gitlabhq/gitlabhq#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
+To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
-The [issue tracker](https://github.com/gitlabhq/gitlabhq/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a pull request which partially or fully addresses the issue.
+The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md).
+If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request.
+When submitting an issue please conform to the issue submission guidelines listed below.
+Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
-Do not use the issue tracker for feature requests. We have a specific [feedback and suggestions forum](http://feedback.gitlab.com) for this purpose.
+Issues can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) or [github.com](https://github.com/gitlabhq/gitlabhq/issues).
-Please send a pull request with a tested solution or a pull request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
+Do not use the issue tracker for feature requests.
+We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
+Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
+
+Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
### Issue tracker guidelines
-**[Search](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
+**[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
-2. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: `vagrant destroy && vagrant up && vagrant ssh`)
+2. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab development virtual machine with vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) (start your issue with: `vagrant destroy && vagrant up && vagrant ssh`)
3. **Expected behavior:** Describe your issue in detail
4. **Observed behavior**
5. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
@@ -43,37 +53,64 @@ Please send a pull request with a tested solution or a pull request with a faili
* Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
7. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
-## Pull requests
+## Merge requests
-We welcome pull requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a pull request for are listed with the [status 'accepting merge/pull requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome.
+We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge requests' on our feature request forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. If you want to add a new feature that is not marked it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it to be marked accepting merge requests. Please include screenshots or wireframes if the feature will also change the UI.
-### Pull request guidelines
+Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
-If you can, please submit a pull request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a pull request is as follows:
+### Merge request guidelines
-1. Fork the project on GitHub
+If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
+
+1. Fork the project on GitLab Cloud
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
-1. Submit a pull request
-2. [Search for issues](https://github.com/gitlabhq/gitlabhq/search?q=&ref=cmdform&type=Issues) related to your pull request and mention them in the pull request description
+1. Submit a merge request (MR) to the master branch
+1. The MR title should describes the change you want to make
+1. The MR description should give a motive for your change and the method you used to achieve it
+1. If the MR changes the UI it should include before and after screenshots
+1. If the MR changes CSS classes please include the list of affected pages `grep css-class ./app -R`
+1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
+1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
+1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
-We will accept pull requests if:
+The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
-* The code has proper tests and all tests pass (or it is a test exposing a failure in existing code)
-* It can be merged without problems (if not please use: `git rebase master`)
-* It does not break any existing functionality
-* It's quality code that conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices
-* The description includes a motive for your change and the method you used to achieve it
-* It is not a catch all pull request but rather fixes a specific issue or implements a specific feature
-* It keeps the GitLab code base clean and well structured
-* We think other users will benefit from the same functionality
-* If it makes changes to the UI the pull request should include screenshots
-* It is a single commit (please use `git rebase -i` to squash commits)
+Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it.
-For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
+For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). Please ensure that your merge request meets the following contribution acceptance criteria.
-## Security vulnerabilities
-Please report security vulnerabilities in private to support@gitlab.com; also see http://www.gitlab.com/disclosure/. Do NOT create GitHub issues for security vulnerabilities.
+**Please format your merge request description as follows:**
+
+1. What does this MR do?
+2. Are there points in the code the reviewer needs to double check?
+3. Why was this MR needed?
+4. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
+5. Screenshots (If appropiate)
+
+## Contribution acceptance criteria
+
+1. The change is as small as possible (see the above paragraph for details)
+1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
+1. Can merge without problems (if not please use: `git rebase master`)
+1. Does not break any existing functionality
+1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
+1. Keeps the GitLab code base clean and well structured
+1. Contains functionality we think other users will benefit from too
+1. Doesn't add configuration options since they complicate future changes
+1. Contains a single commit (please use `git rebase -i` to squash commits)
+1. It conforms to the following style guides
+
+## Style guides
+1. [Ruby](https://github.com/bbatsov/ruby-style-guide)
+1. [Rails](https://github.com/bbatsov/rails-style-guide)
+1. [Formatting](https://github.com/thoughtbot/guides/tree/master/style#formatting)
+1. [Naming](https://github.com/thoughtbot/guides/tree/master/style#naming)
+1. [Testing](https://github.com/thoughtbot/guides/tree/master/style#testing)
+1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript)
+1. [Shell commands](doc/development/shell_commands.md)
+1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
diff --git a/Gemfile b/Gemfile
index a543c5e3a6..7ff7515143 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,15 +8,21 @@ def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
-gem "rails", "3.2.15"
+gem "rails", "~> 4.0.0"
+
+gem "protected_attributes"
+gem 'rails-observers'
+
+# Default values for AR models
+gem "default_value_for", "~> 3.0.0"
# Supported DBs
gem "mysql2", group: :mysql
gem "pg", group: :postgres
# Auth
-gem "devise", '~> 2.2'
-gem "devise-async"
+gem "devise", '3.0.4'
+gem "devise-async", '0.8.0'
gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
@@ -24,26 +30,28 @@ gem 'omniauth-github'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", "~> 3.0.0.rc2"
+gem "gitlab_git", '~> 5.8'
# Ruby/Rack Git Smart-HTTP Server Handler
-gem 'gitlab-grack', '~> 1.0.1', require: 'grack'
+gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap"
-
-# Syntax highlighter
-gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
+gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap"
# Git Wiki
-gem "gitlab-gollum-lib", "~> 1.0.1", require: 'gollum-lib'
+gem 'gollum-lib', '~> 3.0.0'
# Language detection
-gem "github-linguist", require: "linguist"
+gem "gitlab-linguist", "~> 3.0.0", require: "linguist"
# API
-gem "grape", "~> 0.4.1"
-gem "grape-entity", "~> 0.3.0"
+gem "grape", "~> 0.6.1"
+# Replace with rubygems when nesteted entities get released
+gem "grape-entity", "~> 0.4.2"
+gem 'rack-cors', require: 'rack/cors'
+
+# Email validation
+gem "email_validator", "~> 1.4.0", :require => 'email_validator/strict'
# Format dates and times
# based on human-friendly examples
@@ -53,7 +61,7 @@ gem "stamp"
gem 'enumerize'
# Pagination
-gem "kaminari", "~> 0.14.1"
+gem "kaminari", "~> 0.15.1"
# HAML
gem "haml-rails"
@@ -62,7 +70,8 @@ gem "haml-rails"
gem "carrierwave"
# for aws storage
-gem "fog", "~> 1.3.1", group: :aws
+gem "fog", "~> 1.14", group: :aws
+gem "unf", group: :aws
# Authorization
gem "six"
@@ -72,13 +81,19 @@ gem "seed-fu"
# Markdown to HTML
gem "redcarpet", "~> 2.2.2"
-gem "github-markup", "~> 0.7.4", require: 'github/markup'
+gem "github-markup"
+
+# Diffs
+gem 'diffy', '~> 3.0.3'
# Asciidoc to HTML
gem "asciidoctor"
# Application server
-gem "unicorn", '~> 4.6.3', group: :unicorn
+group :unicorn do
+ gem "unicorn", '~> 4.6.3'
+ gem 'unicorn-worker-killer'
+end
# State machine
gem "state_machine"
@@ -89,7 +104,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
-gem 'sidekiq'
+gem 'sidekiq', '2.17.0'
# HTTP requests
gem "httparty"
@@ -102,6 +117,7 @@ gem 'settingslogic'
# Misc
gem "foreman"
+gem 'version_sorter'
# Cache
gem "redis-rails"
@@ -110,11 +126,17 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2'
# HipChat integration
-gem "hipchat", "~> 0.9.0"
+gem "hipchat", "~> 0.14.0"
# Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
+# Gemnasium integration
+gem "gemnasium-gitlab-service", "~> 0.2"
+
+# Slack integration
+gem "slack-notifier", "~> 0.3.2"
+
# d3
gem "d3_rails", "~> 3.1.4"
@@ -122,37 +144,37 @@ gem "d3_rails", "~> 3.1.4"
gem "underscore-rails", "~> 1.4.4"
# Sanitize user input
-gem "sanitize"
+gem "sanitize", '~> 2.0'
# Protect against bruteforcing
gem "rack-attack"
-group :assets do
- gem "sass-rails"
- gem "coffee-rails"
- gem "uglifier"
- gem "therubyracer"
- gem 'turbolinks'
- gem 'jquery-turbolinks'
+# Ace editor
+gem 'ace-rails-ap'
- gem 'chosen-rails', "1.0.0"
- gem 'select2-rails'
- gem 'jquery-atwho-rails', "0.3.0"
- gem "jquery-rails", "2.1.3"
- gem "jquery-ui-rails", "2.0.2"
- gem "modernizr", "2.6.2"
- gem "raphael-rails", "~> 2.1.2"
- gem 'bootstrap-sass'
- gem "font-awesome-rails"
- gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
- gem "gon"
-end
+gem "sass-rails", '~> 4.0.2'
+gem "coffee-rails"
+gem "uglifier"
+gem "therubyracer"
+gem 'turbolinks'
+gem 'jquery-turbolinks'
+
+gem 'select2-rails'
+gem 'jquery-atwho-rails', "~> 0.3.3"
+gem "jquery-rails"
+gem "jquery-ui-rails"
+gem "raphael-rails", "~> 2.1.2"
+gem 'bootstrap-sass', '~> 3.0'
+gem "font-awesome-rails", '~> 3.2'
+gem "gitlab_emoji", "~> 0.0.1.1"
+gem "gon", '~> 5.0.0'
+gem 'nprogress-rails'
group :development do
gem "annotate", "~> 2.6.0.beta2"
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
- gem 'rack-mini-profiler'
+ gem 'rack-mini-profiler', require: false
# Better errors handler
gem 'better_errors'
@@ -169,7 +191,7 @@ end
group :development, :test do
gem 'coveralls', require: false
- gem 'rails-dev-tweaks'
+ # gem 'rails-dev-tweaks'
gem 'spinach-rails'
gem "rspec-rails"
gem "capybara"
@@ -197,8 +219,11 @@ group :development, :test do
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.4.1'
- gem 'spork', '~> 1.0rc'
- gem 'jasmine'
+ gem 'jasmine', '2.0.0.rc5'
+
+ gem "spring", '1.1.1'
+ gem "spring-commands-rspec", '1.0.1'
+ gem "spring-commands-spinach", '1.0.0'
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 0b4bf8fe32..f5f31105e1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,198 +1,201 @@
GEM
remote: https://rubygems.org/
specs:
- actionmailer (3.2.15)
- actionpack (= 3.2.15)
+ ace-rails-ap (2.0.1)
+ actionmailer (4.0.5)
+ actionpack (= 4.0.5)
mail (~> 2.5.4)
- actionpack (3.2.15)
- activemodel (= 3.2.15)
- activesupport (= 3.2.15)
- builder (~> 3.0.0)
+ actionpack (4.0.5)
+ activesupport (= 4.0.5)
+ builder (~> 3.1.0)
erubis (~> 2.7.0)
- journey (~> 1.0.4)
- rack (~> 1.4.5)
- rack-cache (~> 1.2)
- rack-test (~> 0.6.1)
- sprockets (~> 2.2.1)
- activemodel (3.2.15)
- activesupport (= 3.2.15)
- builder (~> 3.0.0)
- activerecord (3.2.15)
- activemodel (= 3.2.15)
- activesupport (= 3.2.15)
- arel (~> 3.0.2)
- tzinfo (~> 0.3.29)
- activeresource (3.2.15)
- activemodel (= 3.2.15)
- activesupport (= 3.2.15)
- activesupport (3.2.15)
- i18n (~> 0.6, >= 0.6.4)
- multi_json (~> 1.0)
+ rack (~> 1.5.2)
+ rack-test (~> 0.6.2)
+ activemodel (4.0.5)
+ activesupport (= 4.0.5)
+ builder (~> 3.1.0)
+ activerecord (4.0.5)
+ activemodel (= 4.0.5)
+ activerecord-deprecated_finders (~> 1.0.2)
+ activesupport (= 4.0.5)
+ arel (~> 4.0.0)
+ activerecord-deprecated_finders (1.0.3)
+ activesupport (4.0.5)
+ i18n (~> 0.6, >= 0.6.9)
+ minitest (~> 4.2)
+ multi_json (~> 1.3)
+ thread_safe (~> 0.1)
+ tzinfo (~> 0.3.37)
acts-as-taggable-on (2.4.1)
rails (>= 3, < 5)
- addressable (2.3.4)
- annotate (2.6.0.beta2)
+ addressable (2.3.5)
+ annotate (2.6.0)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
- arel (3.0.2)
- asciidoctor (0.1.3)
+ arel (4.0.2)
+ asciidoctor (0.1.4)
awesome_print (1.2.0)
- backports (3.3.2)
- bcrypt-ruby (3.1.1)
+ axiom-types (0.0.5)
+ descendants_tracker (~> 0.0.1)
+ ice_nine (~> 0.9)
+ bcrypt-ruby (3.1.2)
better_errors (1.0.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- bootstrap-sass (2.3.2.2)
+ bootstrap-sass (3.0.3.0)
sass (~> 3.2)
- builder (3.0.4)
+ builder (3.1.4)
capybara (2.1.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
- carrierwave (0.8.0)
+ carrierwave (0.9.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
- celluloid (0.14.1)
- timers (>= 1.0.0)
+ json (>= 1.7)
+ celluloid (0.15.2)
+ timers (~> 1.1.0)
charlock_holmes (0.6.9.4)
- childprocess (0.3.9)
- ffi (~> 1.0, >= 1.0.11)
- chosen-rails (1.0.0)
- coffee-rails (>= 3.2)
- compass-rails (>= 1.0)
- railties (>= 3.0)
- sass-rails (>= 3.2)
- chunky_png (1.2.8)
- cliver (0.2.1)
+ cliver (0.2.2)
code_analyzer (0.4.3)
sexp_processor
- coderay (1.0.9)
- coffee-rails (3.2.2)
+ coderay (1.1.0)
+ coercible (1.0.0)
+ descendants_tracker (~> 0.0.1)
+ coffee-rails (4.0.1)
coffee-script (>= 2.2.0)
- railties (~> 3.2.0)
+ railties (>= 4.0.0, < 5.0)
coffee-script (2.2.0)
coffee-script-source
execjs
- coffee-script-source (1.6.2)
+ coffee-script-source (1.6.3)
colored (1.2)
colorize (0.5.8)
- compass (0.12.2)
- chunky_png (~> 1.2)
- fssm (>= 0.2.7)
- sass (~> 3.1)
- compass-rails (1.0.3)
- compass (>= 0.12.2, < 0.14)
- connection_pool (1.1.0)
+ connection_pool (1.2.0)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
simplecov (>= 0.7)
term-ansicolor
thor
- crack (0.4.0)
+ crack (0.4.1)
safe_yaml (~> 0.9.0)
d3_rails (3.1.10)
railties (>= 3.1.0)
daemons (1.1.9)
- database_cleaner (1.1.1)
+ database_cleaner (1.2.0)
debug_inspector (0.0.2)
- descendants_tracker (0.0.1)
- devise (2.2.5)
+ default_value_for (3.0.0)
+ activerecord (>= 3.2.0, < 5.0)
+ descendants_tracker (0.0.3)
+ devise (3.0.4)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
- railties (~> 3.1)
- warden (~> 1.2.1)
+ railties (>= 3.2.6, < 5)
+ warden (~> 1.2.3)
devise-async (0.8.0)
devise (>= 2.2, < 3.2)
- diff-lcs (1.2.4)
- dotenv (0.8.0)
- email_spec (1.4.0)
+ diff-lcs (1.2.5)
+ diffy (3.0.3)
+ docile (1.1.1)
+ dotenv (0.9.0)
+ email_spec (1.5.0)
launchy (~> 2.1)
mail (~> 2.2)
- enumerize (0.6.1)
+ email_validator (1.4.0)
+ activemodel
+ emoji (1.0.1)
+ json
+ enumerize (0.7.0)
activesupport (>= 3.2)
+ equalizer (0.0.8)
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (1.0.3)
- excon (0.13.4)
- execjs (1.4.0)
- multi_json (~> 1.0)
- factory_girl (4.2.0)
+ excon (0.32.1)
+ execjs (2.0.2)
+ factory_girl (4.3.0)
activesupport (>= 3.0.0)
- factory_girl_rails (4.2.1)
- factory_girl (~> 4.2.0)
+ factory_girl_rails (4.3.0)
+ factory_girl (~> 4.3.0)
railties (>= 3.0.0)
- faraday (0.8.7)
- multipart-post (~> 1.1)
+ faraday (0.8.8)
+ multipart-post (~> 1.2.0)
faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9)
- ffaker (1.18.0)
- ffi (1.9.0)
- fog (1.3.1)
+ ffaker (1.22.1)
+ ffi (1.9.3)
+ fog (1.21.0)
+ fog-brightbox
+ fog-core (~> 1.21, >= 1.21.1)
+ fog-json
+ nokogiri (~> 1.5, >= 1.5.11)
+ fog-brightbox (0.0.1)
+ fog-core
+ fog-json
+ fog-core (1.21.1)
builder
- excon (~> 0.13.0)
+ excon (~> 0.32)
formatador (~> 0.2.0)
mime-types
- multi_json (~> 1.0)
- net-scp (~> 1.0.4)
+ net-scp (~> 1.1)
net-ssh (>= 2.1.3)
- nokogiri (~> 1.5.0)
- ruby-hmac
+ fog-json (1.0.0)
+ multi_json (~> 1.0)
font-awesome-rails (3.2.1.3)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.4)
- fssm (0.2.10)
- gemoji (1.2.1)
- gherkin-ruby (0.3.0)
- github-linguist (2.3.4)
- charlock_holmes (~> 0.6.6)
- escape_utils (~> 0.2.3)
- mime-types (~> 1.19)
- pygments.rb (>= 0.2.13)
- github-markdown (0.5.3)
- github-markup (0.7.5)
+ gemnasium-gitlab-service (0.2.1)
+ rugged (~> 0.19)
+ gherkin-ruby (0.3.1)
+ racc
+ github-markup (1.1.0)
gitlab-flowdock-git-hook (0.4.2.2)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-gollum-lib (1.0.1)
- github-markdown (~> 0.5.3)
- github-markup (>= 0.7.5, < 1.0.0)
- gitlab-grit (>= 2.5.1)
- nokogiri (~> 1.5.9)
- pygments.rb (~> 0.4.2)
- sanitize (~> 2.0.3)
- stringex (~> 1.5.1)
- gitlab-grack (1.0.1)
- rack (~> 1.4.1)
- gitlab-grit (2.6.1)
- charlock_holmes (~> 0.6.9)
+ gitlab-grack (2.0.0.pre)
+ rack (~> 1.5.1)
+ gitlab-grit (2.6.7)
+ charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
- posix-spawn (~> 0.3.6)
- gitlab-pygments.rb (0.3.2)
- posix-spawn (~> 0.3.6)
- yajl-ruby (~> 1.1.0)
- gitlab_git (3.0.0.rc2)
- activesupport (~> 3.2.13)
- github-linguist (~> 2.3.4)
- gitlab-grit (~> 2.6.1)
+ posix-spawn (~> 0.3)
+ gitlab-linguist (3.0.0)
+ charlock_holmes (~> 0.6.6)
+ escape_utils (~> 0.2.4)
+ mime-types (~> 1.19)
+ gitlab_emoji (0.0.1.1)
+ emoji (~> 1.0.1)
+ gitlab_git (5.8.0)
+ activesupport (~> 4.0)
+ charlock_holmes (~> 0.6)
+ gitlab-grit (~> 2.6)
+ gitlab-linguist (~> 3.0)
+ rugged (~> 0.19.0)
gitlab_meta (6.0)
- gitlab_omniauth-ldap (1.0.3)
+ gitlab_omniauth-ldap (1.0.4)
net-ldap (~> 0.3.1)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
- gon (4.1.1)
+ gollum-lib (3.0.0)
+ github-markup (~> 1.1.0)
+ gitlab-grit (~> 2.6.5)
+ nokogiri (~> 1.6.1)
+ rouge (~> 1.3.3)
+ sanitize (~> 2.1.0)
+ stringex (~> 2.5.1)
+ gon (5.0.1)
actionpack (>= 2.3.0)
json
- grape (0.4.1)
+ grape (0.6.1)
activesupport
builder
hashie (>= 1.2.0)
@@ -201,92 +204,92 @@ GEM
rack (>= 1.3.0)
rack-accept
rack-mount
- virtus
- grape-entity (0.3.0)
+ virtus (>= 1.0.0)
+ grape-entity (0.4.2)
activesupport
multi_json (>= 1.3.2)
growl (1.0.3)
- guard (1.8.1)
+ guard (2.2.4)
formatador (>= 0.2.4)
- listen (>= 1.0.0)
- lumberjack (>= 1.0.2)
- pry (>= 0.9.10)
- thor (>= 0.14.6)
- guard-rspec (3.0.2)
- guard (>= 1.8)
- rspec (~> 2.13)
+ listen (~> 2.1)
+ lumberjack (~> 1.0)
+ pry (>= 0.9.12)
+ thor (>= 0.18.1)
+ guard-rspec (4.2.0)
+ guard (>= 2.1.1)
+ rspec (>= 2.14, < 4.0)
guard-spinach (0.0.2)
guard (>= 1.1)
spinach
- haml (4.0.3)
+ haml (4.0.4)
tilt
- haml-rails (0.4)
- actionpack (>= 3.1, < 4.1)
- activesupport (>= 3.1, < 4.1)
- haml (>= 3.1, < 4.1)
- railties (>= 3.1, < 4.1)
- hashie (1.2.0)
+ haml-rails (0.5.1)
+ actionpack (~> 4.0.0)
+ activesupport (~> 4.0.0)
+ haml (>= 3.1, < 5.0)
+ railties (~> 4.0.0)
+ hashie (2.0.5)
hike (1.2.3)
- hipchat (0.9.0)
+ hipchat (0.14.0)
httparty
httparty
http_parser.rb (0.5.3)
- httparty (0.11.0)
- multi_json (~> 1.0)
+ httparty (0.13.0)
+ json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.0)
- i18n (0.6.5)
- jasmine (1.3.2)
- jasmine-core (~> 1.3.1)
- rack (~> 1.0)
- rspec (>= 1.3.1)
- selenium-webdriver (>= 0.1.3)
- jasmine-core (1.3.1)
- journey (1.0.4)
- jquery-atwho-rails (0.3.0)
- jquery-rails (2.1.3)
- railties (>= 3.1.0, < 5.0)
- thor (~> 0.14)
- jquery-turbolinks (1.0.0)
+ i18n (0.6.9)
+ ice_nine (0.10.0)
+ jasmine (2.0.0.rc5)
+ jasmine-core (~> 2.0.0.rc5)
+ phantomjs
+ rack (>= 1.2.1)
+ rake
+ jasmine-core (2.0.0.rc5)
+ jquery-atwho-rails (0.3.3)
+ jquery-rails (3.1.0)
+ railties (>= 3.0, < 5.0)
+ thor (>= 0.14, < 2.0)
+ jquery-turbolinks (2.0.1)
railties (>= 3.1.0)
turbolinks
- jquery-ui-rails (2.0.2)
- jquery-rails
- railties (>= 3.1.0)
- json (1.7.7)
+ jquery-ui-rails (4.2.1)
+ railties (>= 3.2.16)
+ json (1.8.1)
jwt (0.1.8)
multi_json (>= 1.5)
- kaminari (0.14.1)
+ kaminari (0.15.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.8.0)
- launchy (2.3.0)
+ kgio (2.8.1)
+ launchy (2.4.2)
addressable (~> 2.3)
- letter_opener (1.1.1)
+ letter_opener (1.1.2)
launchy (~> 2.2)
- libv8 (3.11.8.17)
- listen (1.2.2)
+ libv8 (3.16.14.3)
+ listen (2.3.1)
+ celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
- rb-kqueue (>= 0.2)
- lumberjack (1.0.3)
+ lumberjack (1.0.4)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
- method_source (0.8.1)
- mime-types (1.25)
- minitest (4.7.4)
- modernizr (2.6.2)
- sprockets (~> 2.0)
- multi_json (1.8.2)
- multi_xml (0.5.4)
+ method_source (0.8.2)
+ mime-types (1.25.1)
+ mini_portile (0.5.3)
+ minitest (4.7.5)
+ multi_json (1.10.0)
+ multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.11)
net-ldap (0.3.1)
- net-scp (1.0.4)
- net-ssh (>= 1.99.1)
- net-ssh (2.6.8)
- nokogiri (1.5.10)
+ net-scp (1.1.2)
+ net-ssh (>= 2.6.5)
+ net-ssh (2.8.0)
+ nokogiri (1.6.1)
+ mini_portile (~> 0.5.0)
+ nprogress-rails (0.1.2.3)
oauth (0.4.7)
oauth2 (0.8.1)
faraday (~> 0.8)
@@ -297,10 +300,10 @@ GEM
omniauth (1.1.4)
hashie (>= 1.2, < 3)
rack
- omniauth-github (1.1.0)
+ omniauth-github (1.1.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
- omniauth-google-oauth2 (0.1.19)
+ omniauth-google-oauth2 (0.2.1)
omniauth (~> 1.0)
omniauth-oauth2
omniauth-oauth (1.0.1)
@@ -309,56 +312,53 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
- omniauth-twitter (0.0.17)
+ omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
- orm_adapter (0.4.0)
+ orm_adapter (0.5.0)
pg (0.15.1)
+ phantomjs (1.9.2.0)
poltergeist (1.4.1)
capybara (~> 2.1.0)
cliver (~> 0.2.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
- polyglot (0.3.3)
- posix-spawn (0.3.6)
- pry (0.9.12.2)
- coderay (~> 1.0.5)
+ polyglot (0.3.4)
+ posix-spawn (0.3.8)
+ protected_attributes (1.0.5)
+ activemodel (>= 4.0.1, < 5.0)
+ pry (0.9.12.4)
+ coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
- pygments.rb (0.4.2)
- posix-spawn (~> 0.3.6)
- yajl-ruby (~> 1.1.0)
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
- rack (1.4.5)
+ racc (1.4.10)
+ rack (1.5.2)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (2.2.1)
+ rack-attack (2.3.0)
rack
- rack-cache (1.2)
- rack (>= 0.4)
- rack-mini-profiler (0.1.31)
+ rack-cors (0.2.9)
+ rack-mini-profiler (0.9.0)
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-protection (1.5.0)
- rack
- rack-ssl (1.3.3)
+ rack-protection (1.5.1)
rack
rack-test (0.6.2)
rack (>= 1.0)
- rails (3.2.15)
- actionmailer (= 3.2.15)
- actionpack (= 3.2.15)
- activerecord (= 3.2.15)
- activeresource (= 3.2.15)
- activesupport (= 3.2.15)
- bundler (~> 1.0)
- railties (= 3.2.15)
- rails-dev-tweaks (0.6.1)
- actionpack (~> 3.1)
- railties (~> 3.1)
+ rails (4.0.5)
+ actionmailer (= 4.0.5)
+ actionpack (= 4.0.5)
+ activerecord (= 4.0.5)
+ activesupport (= 4.0.5)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 4.0.5)
+ sprockets-rails (~> 2.0.0)
+ rails-observers (0.1.2)
+ activemodel (~> 4.0)
rails_best_practices (1.14.4)
activesupport
awesome_print
@@ -368,254 +368,267 @@ GEM
i18n
require_all
ruby-progressbar
- railties (3.2.15)
- actionpack (= 3.2.15)
- activesupport (= 3.2.15)
- rack-ssl (~> 1.3.2)
+ railties (4.0.5)
+ actionpack (= 4.0.5)
+ activesupport (= 4.0.5)
rake (>= 0.8.7)
- rdoc (~> 3.4)
- thor (>= 0.14.6, < 2.0)
- raindrops (0.11.0)
- rake (10.1.0)
+ thor (>= 0.18.1, < 2.0)
+ raindrops (0.12.0)
+ rake (10.3.1)
raphael-rails (2.1.2)
rb-fsevent (0.9.3)
- rb-inotify (0.9.0)
- ffi (>= 0.5.0)
- rb-kqueue (0.2.0)
+ rb-inotify (0.9.2)
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
- redis (3.0.4)
- redis-actionpack (3.2.4)
- actionpack (~> 3.2.0)
- redis-rack (~> 1.4.4)
- redis-store (~> 1.1.4)
- redis-activesupport (3.2.4)
- activesupport (~> 3.2.0)
+ redis (3.0.6)
+ redis-actionpack (4.0.0)
+ actionpack (~> 4)
+ redis-rack (~> 1.5.0)
+ redis-store (~> 1.1.0)
+ redis-activesupport (4.0.0)
+ activesupport (~> 4)
+ redis-store (~> 1.1.0)
+ redis-namespace (1.4.1)
+ redis (~> 3.0.4)
+ redis-rack (1.5.0)
+ rack (~> 1.5)
+ redis-store (~> 1.1.0)
+ redis-rails (4.0.0)
+ redis-actionpack (~> 4)
+ redis-activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-namespace (1.3.1)
- redis (~> 3.0.0)
- redis-rack (1.4.4)
- rack (~> 1.4.0)
- redis-store (~> 1.1.4)
- redis-rails (3.2.4)
- redis-actionpack (~> 3.2.4)
- redis-activesupport (~> 3.2.4)
- redis-store (~> 1.1.4)
redis-store (1.1.4)
redis (>= 2.2)
ref (1.0.5)
- require_all (1.3.1)
+ require_all (1.3.2)
rest-client (1.6.7)
mime-types (>= 1.16)
- rspec (2.13.0)
- rspec-core (~> 2.13.0)
- rspec-expectations (~> 2.13.0)
- rspec-mocks (~> 2.13.0)
- rspec-core (2.13.1)
- rspec-expectations (2.13.0)
+ rouge (1.3.3)
+ rspec (2.14.1)
+ rspec-core (~> 2.14.0)
+ rspec-expectations (~> 2.14.0)
+ rspec-mocks (~> 2.14.0)
+ rspec-core (2.14.7)
+ rspec-expectations (2.14.4)
diff-lcs (>= 1.1.3, < 2.0)
- rspec-mocks (2.13.1)
- rspec-rails (2.13.2)
+ rspec-mocks (2.14.4)
+ rspec-rails (2.14.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 2.13.0)
- rspec-expectations (~> 2.13.0)
- rspec-mocks (~> 2.13.0)
- ruby-hmac (0.4.0)
+ rspec-core (~> 2.14.0)
+ rspec-expectations (~> 2.14.0)
+ rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
- rubyzip (0.9.9)
- safe_yaml (0.9.3)
- sanitize (2.0.3)
- nokogiri (>= 1.4.4, < 1.6)
- sass (3.2.11)
- sass-rails (3.2.6)
- railties (~> 3.2.0)
- sass (>= 3.1.10)
- tilt (~> 1.3)
+ rugged (0.19.0)
+ safe_yaml (0.9.7)
+ sanitize (2.1.0)
+ nokogiri (>= 1.4.4)
+ sass (3.2.19)
+ sass-rails (4.0.3)
+ railties (>= 4.0.0, < 5.0)
+ sass (~> 3.2.0)
+ sprockets (~> 2.8, <= 2.11.0)
+ sprockets-rails (~> 2.0)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
- seed-fu (2.2.0)
- activerecord (~> 3.1)
- activesupport (~> 3.1)
- select2-rails (3.4.2)
- sass-rails
+ seed-fu (2.3.0)
+ activerecord (>= 3.1, < 4.1)
+ activesupport (>= 3.1, < 4.1)
+ select2-rails (3.5.2)
thor (~> 0.14)
- selenium-webdriver (2.33.0)
- childprocess (>= 0.2.5)
- multi_json (~> 1.0)
- rubyzip
- websocket (~> 1.0.4)
settingslogic (2.0.9)
- sexp_processor (4.3.0)
+ sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
- sidekiq (2.14.0)
- celluloid (>= 0.14.1)
+ sidekiq (2.17.0)
+ celluloid (>= 0.15.2)
connection_pool (>= 1.0.0)
json
redis (>= 3.0.4)
- redis-namespace
+ redis-namespace (>= 1.3.1)
simple_oauth (0.1.9)
- simplecov (0.7.1)
- multi_json (~> 1.0)
- simplecov-html (~> 0.7.1)
- simplecov-html (0.7.1)
- sinatra (1.4.3)
+ simplecov (0.8.2)
+ docile (~> 1.1.0)
+ multi_json
+ simplecov-html (~> 0.8.0)
+ simplecov-html (0.8.0)
+ sinatra (1.4.4)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
- slim (2.0.0)
- temple (~> 0.6.5)
- tilt (~> 1.3, >= 1.3.3)
- slop (3.4.5)
- spinach (0.8.3)
+ slack-notifier (0.3.2)
+ slim (2.0.2)
+ temple (~> 0.6.6)
+ tilt (>= 1.3.3, < 2.1)
+ slop (3.4.7)
+ spinach (0.8.7)
colorize (= 0.5.8)
- gherkin-ruby (~> 0.3.0)
+ gherkin-ruby (>= 0.3.1)
spinach-rails (0.2.1)
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
- spork (1.0.0rc2)
- sprockets (2.2.2)
+ spring (1.1.1)
+ spring-commands-rspec (1.0.1)
+ spring (>= 0.9.1)
+ spring-commands-spinach (1.0.0)
+ spring (>= 0.9.1)
+ sprockets (2.11.0)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
+ sprockets-rails (2.0.1)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ sprockets (~> 2.8)
stamp (0.5.0)
state_machine (1.2.0)
- stringex (1.5.1)
- temple (0.6.5)
+ stringex (2.5.1)
+ temple (0.6.7)
term-ansicolor (1.2.2)
tins (~> 0.8)
- test_after_commit (0.2.1)
- therubyracer (0.11.4)
- libv8 (~> 3.11.8.12)
+ test_after_commit (0.2.2)
+ therubyracer (0.12.0)
+ libv8 (~> 3.16.14.0)
ref
- thin (1.5.1)
+ thin (1.6.1)
daemons (>= 1.0.9)
- eventmachine (>= 0.12.6)
+ eventmachine (>= 1.0.0)
rack (>= 1.0.0)
- thor (0.18.1)
+ thor (0.19.1)
+ thread_safe (0.3.3)
tilt (1.4.1)
timers (1.1.0)
- tinder (1.9.2)
+ tinder (1.9.3)
eventmachine (~> 1.0)
faraday (~> 0.8)
faraday_middleware (~> 0.9)
- hashie (~> 1.0)
- json (~> 1.7.5)
+ hashie (>= 1.0, < 3)
+ json (~> 1.8.0)
mime-types (~> 1.19)
- multi_json (~> 1.5)
+ multi_json (~> 1.7)
twitter-stream (~> 0.1)
- tins (0.11.0)
+ tins (0.13.1)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
- turbolinks (1.2.0)
+ turbolinks (2.0.0)
coffee-rails
twitter-stream (0.1.16)
eventmachine (>= 0.12.8)
http_parser.rb (~> 0.5.1)
simple_oauth (~> 0.1.4)
- tzinfo (0.3.38)
- uglifier (2.1.1)
+ tzinfo (0.3.39)
+ uglifier (2.3.2)
execjs (>= 0.3.0)
- multi_json (~> 1.0, >= 1.0.2)
+ json (>= 1.8.0)
underscore-rails (1.4.4)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.6)
unicorn (4.6.3)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
- virtus (0.5.5)
- backports (~> 3.3)
+ unicorn-worker-killer (0.4.2)
+ unicorn (~> 4)
+ version_sorter (1.1.0)
+ virtus (1.0.1)
+ axiom-types (~> 0.0.5)
+ coercible (~> 1.0)
descendants_tracker (~> 0.0.1)
+ equalizer (~> 0.0.7)
warden (1.2.3)
rack (>= 1.0)
- webmock (1.11.0)
+ webmock (1.16.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
- websocket (1.0.7)
- websocket-driver (0.3.0)
+ websocket-driver (0.3.1)
xpath (2.0.0)
nokogiri (~> 1.3)
- yajl-ruby (1.1.0)
PLATFORMS
ruby
DEPENDENCIES
+ ace-rails-ap
acts-as-taggable-on
annotate (~> 2.6.0.beta2)
asciidoctor
awesome_print
better_errors
binding_of_caller
- bootstrap-sass
+ bootstrap-sass (~> 3.0)
capybara
carrierwave
- chosen-rails (= 1.0.0)
coffee-rails
colored
coveralls
d3_rails (~> 3.1.4)
database_cleaner
- devise (~> 2.2)
- devise-async
+ default_value_for (~> 3.0.0)
+ devise (= 3.0.4)
+ devise-async (= 0.8.0)
+ diffy (~> 3.0.3)
email_spec
+ email_validator (~> 1.4.0)
enumerize
factory_girl_rails
ffaker
- fog (~> 1.3.1)
- font-awesome-rails
+ fog (~> 1.14)
+ font-awesome-rails (~> 3.2)
foreman
- gemoji (~> 1.2.1)
- github-linguist
- github-markup (~> 0.7.4)
+ gemnasium-gitlab-service (~> 0.2)
+ github-markup
gitlab-flowdock-git-hook (~> 0.4.2)
- gitlab-gollum-lib (~> 1.0.1)
- gitlab-grack (~> 1.0.1)
- gitlab-pygments.rb (~> 0.3.2)
- gitlab_git (~> 3.0.0.rc2)
+ gitlab-grack (~> 2.0.0.pre)
+ gitlab-linguist (~> 3.0.0)
+ gitlab_emoji (~> 0.0.1.1)
+ gitlab_git (~> 5.8)
gitlab_meta (= 6.0)
- gitlab_omniauth-ldap (= 1.0.3)
- gon
- grape (~> 0.4.1)
- grape-entity (~> 0.3.0)
+ gitlab_omniauth-ldap (= 1.0.4)
+ gollum-lib (~> 3.0.0)
+ gon (~> 5.0.0)
+ grape (~> 0.6.1)
+ grape-entity (~> 0.4.2)
growl
guard-rspec
guard-spinach
haml-rails
- hipchat (~> 0.9.0)
+ hipchat (~> 0.14.0)
httparty
- jasmine
- jquery-atwho-rails (= 0.3.0)
- jquery-rails (= 2.1.3)
+ jasmine (= 2.0.0.rc5)
+ jquery-atwho-rails (~> 0.3.3)
+ jquery-rails
jquery-turbolinks
- jquery-ui-rails (= 2.0.2)
- kaminari (~> 0.14.1)
+ jquery-ui-rails
+ kaminari (~> 0.15.1)
launchy
letter_opener
minitest (~> 4.7.0)
- modernizr (= 2.6.2)
mysql2
+ nprogress-rails
omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
omniauth-twitter
pg
poltergeist (~> 1.4.1)
+ protected_attributes
pry
quiet_assets (~> 1.0.1)
rack-attack
+ rack-cors
rack-mini-profiler
- rails (= 3.2.15)
- rails-dev-tweaks
+ rails (~> 4.0.0)
+ rails-observers
rails_best_practices
raphael-rails (~> 2.1.2)
rb-fsevent
@@ -623,20 +636,23 @@ DEPENDENCIES
redcarpet (~> 2.2.2)
redis-rails
rspec-rails
- sanitize
- sass-rails
+ sanitize (~> 2.0)
+ sass-rails (~> 4.0.2)
sdoc
seed-fu
select2-rails
settingslogic
shoulda-matchers (~> 2.1.0)
- sidekiq
+ sidekiq (= 2.17.0)
simplecov
sinatra
six
+ slack-notifier (~> 0.3.2)
slim
spinach-rails
- spork (~> 1.0rc)
+ spring (= 1.1.1)
+ spring-commands-rspec (= 1.0.1)
+ spring-commands-spinach (= 1.0.0)
stamp
state_machine
test_after_commit
@@ -646,5 +662,8 @@ DEPENDENCIES
turbolinks
uglifier
underscore-rails (~> 1.4.4)
+ unf
unicorn (~> 4.6.3)
+ unicorn-worker-killer
+ version_sorter
webmock
diff --git a/Guardfile b/Guardfile
index e682f0b6cf..e19a312377 100644
--- a/Guardfile
+++ b/Guardfile
@@ -1,7 +1,7 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
-guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
+guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
diff --git a/LICENSE b/LICENSE
index 7cecc2485f..8ebd322ffc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011 Dmitriy Zaporozhets
+Copyright (c) 2011-2014 Dmitriy Zaporozhets
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 0dca62974c..95d9f0a5e7 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -21,3 +21,5 @@ release where the minor version is increased numerically by increments of one
(eg. `5.0 -> 5.1`).
We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable.
+
+More information about the release procedures can be found in the doc/release directory.
diff --git a/PROCESS.md b/PROCESS.md
index 668cacc870..23dbfd0c69 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -7,26 +7,19 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Common actions
### Issue team
-- Looks for issues without workflow labels and triages issue
-- Monitors pull requests
-- Closes invalid issues and pull requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
-- Assigns appropriate [labels](#how-we-handle-issues)
-- Asks for feedback from issue reporter/pull request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
-- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/)
-- Monitors all issues/pull requests for feedback (but especially ones commented on since automatically watching them):
+- Looks for issues without [workflow labels](#how-we-handle-issues) and triages issue
+- Closes invalid issues with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
+- Asks for feedback from issue reporter ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
+- Monitors all issues for feedback (but especially ones commented on since automatically watching them)
- Closes issues with no feedback from the reporter for two weeks
-- Closes stale pull requests
-### Development team
+### Merge request officers
-- Responds to issues and pull requests the issue team mentions them in
-- Monitors for new issues in _Awaiting developer action/feedback_ with no developer activity (once a week)
-- Monitors for new pull requests (at least once a week)
-- Manages their work queue by looking at issues and pull requests assigned to them
-- Close fixed issues (via commit messages or manually)
-- Codes [new features](http://feedback.gitlab.com/forums/176466-general/filters/top)!
-- Response guidelines
-- Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to pull requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
+- Responds to merge requests the issue team mentions them in and monitors for new merge requests
+- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
+- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
+- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
+- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
@@ -37,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people
-The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](http://gitlab.org/team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
+The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
## Workflow labels
@@ -45,8 +38,8 @@ Workflow labels are purposely not very detailed since that would be hard to keep
- _Awaiting feedback_: Feedback pending from the reporter
- _Awaiting confirmation of fix_: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away)
-- _Attached PR_: There is a PR attached and the discussion should happen there
- - We need to let issues stay in sync with the PR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the PR. We can't close the issue when there is a pull request because sometimes a PR is not good and we just close the PR, then the issue must stay.
+- _Attached MR_: There is a MR attached and the discussion should happen there
+ - We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay.
- _Awaiting developer action/feedback_: Issue needs to be fixed or clarified by a developer
## Functional labels
@@ -59,29 +52,33 @@ If an issue is complex and needs the attention of a specific person, assignment
## Label colors
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix)
-- Bright orange `#eb6420`: workflow labels for core team members (attached PR, awaiting developer action/feedback)
+- Bright orange `#eb6420`: workflow labels for core team members (attached MR, awaiting developer action/feedback)
- Light blue `#82C5FF`: functional labels
- Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately:
- Feature request (see copy & paste response: [Feature requests](#feature-requests))
- Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions)
+## Be kind
+
+Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
+
## Copy & paste responses
### Improperly formatted issue
-Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Feature requests
-Thanks for your interest in GitLab. We don't use the GitHub issue tracker for feature requests. Please use http://feedback.gitlab.com/ for this purpose or create a pull request implementing this feature. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information.
+Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the [feature request forum](http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Issue report for old version
-Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Support requests and configuration questions
-Thanks for your interest in GitLab. We don't use the GitHub issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the unofficial #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) for more information.
+Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Code format
@@ -89,17 +86,24 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
### Issue fixed in newer version
-Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(http://blog.gitlab.org/). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
-### Improperly formatted pull request
+### Improperly formatted merge request
-Thanks for your interest in improving the GitLab codebase! Please update your pull request according to the \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-request-guidelines).
+Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines).
### Inactivity close of an issue
-It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
-### Inactivity close of a pull request
+### Inactivity close of a merge request
-This pull request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the pull request guidelines in our \[contributing guidelines\]\(https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this pull request.
+This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request.
+### Accepting merge requests
+
+Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this?
+If so, can you make a comment with a link to it?
+Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab.
+You might be asked to make changes and even after implementing them your feature might still be declined.
+If you want to reduce the chance of this happening please have a discussion in the forum first.
diff --git a/Procfile b/Procfile
index 9003369c93..a5693f8dbc 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,2 @@
-web: bundle exec unicorn_rails -p $PORT -E development
+web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell
diff --git a/README.md b/README.md
index 64e40fdc51..efac636c24 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,45 @@
## GitLab: self hosted Git management software
-
+

-### GitLab allows you to
- * keep your code secure on your own server
- * manage repositories, users and access permissions
- * communicate through issues, line-comments and wiki pages
- * perform code review with merge requests
+### Gitlab is open source software to collaborate on code
-### GitLab is
+* Manage git repositories with fine grained access controls that keep your code secure
+* Perform code reviews and enhance collaboration with merge requests
+* Each project can also have an issue tracker and a wiki
+* Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises
+* Completely free and open source (MIT Expat license)
+* Powered by Ruby on Rails
-* powered by Ruby on Rails
-* completely free and open source (MIT license)
-* used by more than 25.000 organizations to keep their code secure
+### Canonical source
+
+* The source of GitLab Communinity Edition is [hosted on GitLab Cloud](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
### Code status
-* [](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+* [](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
* [](https://codeclimate.com/github/gitlabhq/gitlabhq)
-* [](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available), gems are updated in major releases of GitLab.
-
* [](https://coveralls.io/r/gitlabhq/gitlabhq)
+* [](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
+
### Resources
-* GitLab.org community site: [Homepage](http://gitlab.org) | [Screenshots](http://gitlab.org/screenshots/) | [Blog](http://blog.gitlab.org/) | [Demo](http://demo.gitlabhq.com/users/sign_in)
+* [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/).
-* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/)
+* [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) offers additional features aimed at larger organizations.
-* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
+* [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab.
+
+* Unofficial third-party [iPhone app](http://gitlabcontrol.com/), [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) and [command line client](https://github.com/drewblessing/gitlab-cli) and [Ruby API wrapper](https://github.com/NARKOZ/gitlab) for GitLab.
### Requirements
-* Ubuntu/Debian**
+* Ubuntu/Debian/CentOS/RHEL**
* ruby 1.9.3+
* git 1.7.10+
* redis 2.0+
@@ -46,39 +49,33 @@
### Installation
-#### Official production installation
+#### Official installation methods
-* [Installation guide for a production server](doc/install/installation.md)
+* [GitLab packages](https://www.gitlab.com/downloads/) **recommended** These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions.
+* [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation on a virtual machine with Vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies.
-#### Official development installation
+* [Manual installation guide](doc/install/installation.md) This guide to set up a production server on Ubuntu offers detailed and complete step-by-step instructions.
-If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies.
+#### Third party one-click installers
-* [Vagrant virtual machine for development](https://github.com/gitlabhq/gitlab-vagrant-vm)
+* [Digital Ocean 1-Click Application Install](https://www.digitalocean.com/blog_posts/host-your-git-repositories-in-55-seconds-with-gitlab) Have a new server up in 55 seconds. Digital Ocean uses SSD disks which is great for an IO intensive app such as GitLab. We recommend selecting a droplet with [1GB of memory](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md).
+* [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.).
-#### Unofficial production installations
+#### Unofficial installation methods
-* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version.
+* [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version.
* [Installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) public wiki with unofficial guides to install GitLab on different operating systems.
-* [BitNami one-click installers](http://bitnami.com/stack/gitlab)
-
-* [TurnKey Linux virtual appliance](http://www.turnkeylinux.org/gitlab)
-
-
### New versions and upgrading
-Since 2011 GitLab is released on the 22nd of every month. Every new release includes an upgrade guide.
+Since 2011 GitLab is released on the 22nd of every month. Every new release includes an [upgrade guide](doc/update) and new features are detailed in the [Changelog](CHANGELOG).
-* [Upgrade guides](doc/update)
-
-* [Changelog](CHANGELOG)
-
-* Features that will be in the next releases are listed on [the feedback and suggestions forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
+It is recommended to follow a monthly upgrade schedule. Security releases come out when needed. For more information about the release process see the documentation for [monthly](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/monthly.md) and [security](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/release/security.md) releases.
+* Features that will be in the next releases are listed on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
### Run in production mode
@@ -90,8 +87,16 @@ or by directly calling the script
sudo /etc/init.d/gitlab start
+Please login with root / 5iveL!fe
+
### Run in development mode
+Consider setting up the development environment with [the cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md#installation).
+
+Copy the example development unicorn configuration file
+
+ cp config/unicorn.rb.example.development config/unicorn.rb
+
Start it with [Foreman](https://github.com/ddollar/foreman)
bundle exec foreman start -p 3000
@@ -101,16 +106,13 @@ or start each component separately
bundle exec rails s
script/background_jobs start
+And surf to [localhost:3000](http://localhost:3000/) and login with root / 5iveL!fe
+
### Run the tests
-* Seed the database
-
- bundle exec rake db:setup RAILS_ENV=test
- bundle exec rake db:seed_fu RAILS_ENV=test
-
* Run all tests
- bundle exec rake gitlab:test
+ bundle exec rake test
* [RSpec](http://rspec.info/) unit and functional tests
@@ -125,43 +127,10 @@ or start each component separately
Single Spinach test: bundle exec spinach features/project/issues/milestones.feature
-### GitLab interfaces
+### Documentation
-* [GitLab API doc](doc/api/README.md) or see the [GitLab API website](http://api.gitlab.org/)
-
-* [Rake tasks](doc/raketasks) including a [backup and restore procedure](doc/raketasks/backup_restore.md)
-
-* [Directory structure](doc/install/structure.md)
-
-* [Database installation](doc/install/databases.md)
-
-* [Markdown specification](doc/markdown/markdown.md)
-
-* [Security guide](doc/security/rack_attack.md) to throttle abusive requests
+All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
### Getting help
-* [Maintenance policy](MAINTENANCE.md) specifies what versions are supported.
-
-* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
-
-* [Mailing list](https://groups.google.com/forum/#!forum/gitlabhq) and [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) are the best places to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and has resolved it. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
-
-* [Unofficial #gitlab IRC on Freenode](http://www.freenode.net/) is another way to get in touch with other GitLab users who may be able to help you.
-
-* [Feedback and suggestions forum](http://feedback.gitlab.com) is the place to propose and discuss new features for GitLab.
-
-* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed.
-
-* [Support subscription](http://www.gitlab.com/subscription/) connects you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
-
-* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab experts for installations, upgrades and customizations.
-
-
-### Getting in touch
-
-* [Core team](http://gitlab.org/team/)
-
-* [Contributors](http://contributors.gitlab.org/)
-
-* [Community](http://gitlab.org/community/)
+Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help.
diff --git a/VERSION b/VERSION
index 5a903253f4..c0ad52beda 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-6.3.0.pre
+6.9.0.pre
diff --git a/app/assets/images/ajax_loader.gif b/app/assets/images/ajax_loader.gif
deleted file mode 100644
index 8a97c91987..0000000000
Binary files a/app/assets/images/ajax_loader.gif and /dev/null differ
diff --git a/app/assets/images/ajax_loader_gray.gif b/app/assets/images/ajax_loader_gray.gif
deleted file mode 100644
index af3f618bd0..0000000000
Binary files a/app/assets/images/ajax_loader_gray.gif and /dev/null differ
diff --git a/app/assets/images/ajax_loader_tree.gif b/app/assets/images/ajax_loader_tree.gif
deleted file mode 100644
index 99d5a0f37f..0000000000
Binary files a/app/assets/images/ajax_loader_tree.gif and /dev/null differ
diff --git a/app/assets/images/authbuttons/github_32.png b/app/assets/images/authbuttons/github_32.png
new file mode 100644
index 0000000000..c56eef05eb
Binary files /dev/null and b/app/assets/images/authbuttons/github_32.png differ
diff --git a/vendor/assets/images/authbuttons/github_64.png b/app/assets/images/authbuttons/github_64.png
similarity index 97%
rename from vendor/assets/images/authbuttons/github_64.png
rename to app/assets/images/authbuttons/github_64.png
index fca7bf4465..39de55bc79 100644
Binary files a/vendor/assets/images/authbuttons/github_64.png and b/app/assets/images/authbuttons/github_64.png differ
diff --git a/app/assets/images/authbuttons/google_32.png b/app/assets/images/authbuttons/google_32.png
new file mode 100644
index 0000000000..6225cc9c2d
Binary files /dev/null and b/app/assets/images/authbuttons/google_32.png differ
diff --git a/app/assets/images/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png
new file mode 100644
index 0000000000..4d608f7100
Binary files /dev/null and b/app/assets/images/authbuttons/google_64.png differ
diff --git a/app/assets/images/authbuttons/twitter_32.png b/app/assets/images/authbuttons/twitter_32.png
new file mode 100644
index 0000000000..696eb02484
Binary files /dev/null and b/app/assets/images/authbuttons/twitter_32.png differ
diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png
new file mode 100644
index 0000000000..2893274766
Binary files /dev/null and b/app/assets/images/authbuttons/twitter_64.png differ
diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png
index 8759ca589f..9ecdaf4e2d 100644
Binary files a/app/assets/images/bg-header.png and b/app/assets/images/bg-header.png differ
diff --git a/vendor/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png
similarity index 75%
rename from vendor/assets/images/bg_fallback.png
rename to app/assets/images/bg_fallback.png
index 4b2754b804..d9066ad7d7 100644
Binary files a/vendor/assets/images/bg_fallback.png and b/app/assets/images/bg_fallback.png differ
diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png
index 055a9069b6..6dac6cd8ca 100644
Binary files a/app/assets/images/dark-scheme-preview.png and b/app/assets/images/dark-scheme-preview.png differ
diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico
index 057f74ac7a..bfb74960c4 100644
Binary files a/app/assets/images/favicon.ico and b/app/assets/images/favicon.ico differ
diff --git a/app/assets/images/file_txt.png b/app/assets/images/file_txt.png
index f3638cb4e1..b3230b5add 100644
Binary files a/app/assets/images/file_txt.png and b/app/assets/images/file_txt.png differ
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
new file mode 100644
index 0000000000..32ade0fe9a
Binary files /dev/null and b/app/assets/images/icon-link.png differ
diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png
index 7632915cac..084b89e3a7 100644
Binary files a/app/assets/images/icon-search.png and b/app/assets/images/icon-search.png differ
diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png
new file mode 100644
index 0000000000..9ad65fc443
Binary files /dev/null and b/app/assets/images/icon_sprite.png differ
diff --git a/app/assets/images/images.png b/app/assets/images/images.png
index 973d3bdd39..da91f6b1f4 100644
Binary files a/app/assets/images/images.png and b/app/assets/images/images.png differ
diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png
index 6567f2e546..4a96572d57 100644
Binary files a/app/assets/images/logo-black.png and b/app/assets/images/logo-black.png differ
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png
index a63fb1c9c0..bc2ef601a5 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/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png
index 9477941778..3aeed886a0 100644
Binary files a/app/assets/images/monokai-scheme-preview.png and b/app/assets/images/monokai-scheme-preview.png differ
diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png
index 752d26adba..dac3ab1bb8 100644
Binary files a/app/assets/images/no_avatar.png and b/app/assets/images/no_avatar.png differ
diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png
new file mode 100644
index 0000000000..a97d451598
Binary files /dev/null and b/app/assets/images/no_group_avatar.png differ
diff --git a/app/assets/images/onion_skin_sprites.gif b/app/assets/images/onion_skin_sprites.gif
index 85d20260ed..337aa1bfb6 100644
Binary files a/app/assets/images/onion_skin_sprites.gif and b/app/assets/images/onion_skin_sprites.gif differ
diff --git a/app/assets/images/progress_bar.gif b/app/assets/images/progress_bar.gif
new file mode 100644
index 0000000000..c3d43fa40b
Binary files /dev/null and b/app/assets/images/progress_bar.gif differ
diff --git a/vendor/assets/images/slider_handles.png b/app/assets/images/slider_handles.png
similarity index 61%
rename from vendor/assets/images/slider_handles.png
rename to app/assets/images/slider_handles.png
index b95a46eca9..a6d477033f 100644
Binary files a/vendor/assets/images/slider_handles.png and b/app/assets/images/slider_handles.png differ
diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png
index 728964bc4c..ae092ab521 100644
Binary files a/app/assets/images/solarized-dark-scheme-preview.png and b/app/assets/images/solarized-dark-scheme-preview.png differ
diff --git a/app/assets/images/swipemode_sprites.gif b/app/assets/images/swipemode_sprites.gif
index 327b3c31ff..b010b4e448 100644
Binary files a/app/assets/images/swipemode_sprites.gif and b/app/assets/images/swipemode_sprites.gif differ
diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png
index 7c11f20659..6b8bde41bc 100644
Binary files a/app/assets/images/switch_icon.png and b/app/assets/images/switch_icon.png differ
diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif
index c7e98e044f..5f6ed04a43 100644
Binary files a/app/assets/images/trans_bg.gif and b/app/assets/images/trans_bg.gif differ
diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png
index 67eb876304..d1866e0015 100644
Binary files a/app/assets/images/white-scheme-preview.png and b/app/assets/images/white-scheme-preview.png differ
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index 6230fe7f93..6634bb6cc3 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -8,6 +8,23 @@ class Admin
else
elems.removeAttr 'disabled'
+ $('body').on 'click', '.js-toggle-colors-link', (e) ->
+ e.preventDefault()
+ $('.js-toggle-colors-link').hide()
+ $('.js-toggle-colors-container').show()
+
+ $('input#broadcast_message_color').on 'input', ->
+ previewColor = $('input#broadcast_message_color').val()
+ $('div.broadcast-message-preview').css('background-color', previewColor)
+
+ $('input#broadcast_message_font').on 'input', ->
+ previewColor = $('input#broadcast_message_font').val()
+ $('div.broadcast-message-preview').css('color', previewColor)
+
+ $('textarea#broadcast_message_message').on 'input', ->
+ previewMessage = $('textarea#broadcast_message_message').val()
+ $('div.broadcast-message-preview span').text(previewMessage)
+
$('.log-tabs a').click (e) ->
e.preventDefault()
$(this).tab('show')
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index a36d944cbc..fafa5cdfaa 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -2,6 +2,8 @@
users_path: "/api/:version/users.json"
user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json"
+ namespaces_path: "/api/:version/namespaces.json"
+ project_users_path: "/api/:version/projects/:id/users.json"
# Get 20 (depends on api) recent notes
# and sort the ascending from oldest to newest
@@ -49,6 +51,37 @@
).done (users) ->
callback(users)
+ # Return project users list. Filtered by query
+ # Only active users retrieved
+ projectUsers: (project_id, query, callback) ->
+ url = Api.buildUrl(Api.project_users_path)
+ url = url.replace(':id', project_id)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ active: true
+ dataType: "json"
+ ).done (users) ->
+ callback(users)
+
+ # Return namespaces list. Filtered by query
+ namespaces: (query, callback) ->
+ url = Api.buildUrl(Api.namespaces_path)
+
+ $.ajax(
+ url: url
+ data:
+ private_token: gon.api_token
+ search: query
+ per_page: 20
+ dataType: "json"
+ ).done (namespaces) ->
+ callback(namespaces)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index 0767b82032..0000000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-//= require jquery
-//= require jquery.ui.all
-//= require jquery_ujs
-//= require jquery.cookie
-//= require jquery.endless-scroll
-//= require jquery.highlight
-//= require jquery.history
-//= require jquery.waitforimages
-//= require jquery.atwho
-//= require jquery.scrollto
-//= require jquery.blockUI
-//= require turbolinks
-//= require jquery.turbolinks
-//= require bootstrap
-//= require modernizr
-//= require chosen-jquery
-//= require select2
-//= require raphael
-//= require g.raphael-min
-//= require g.bar-min
-//= require branch-graph
-//= require ace-src-noconflict/ace
-//= require_tree .
-//= require d3
-//= require underscore
diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/application.js.coffee
similarity index 71%
rename from app/assets/javascripts/main.js.coffee
rename to app/assets/javascripts/application.js.coffee
index ccd5ad48db..a22ff6dec3 100644
--- a/app/assets/javascripts/main.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -1,5 +1,35 @@
-window.updatePage = (data) ->
- $.ajax({type: "GET", url: location.href, data: data, dataType: "script"})
+# This is a manifest file that'll be compiled into including all the files listed below.
+# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+# be included in the compiled file accessible from http://example.com/assets/application.js
+# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+# the compiled file.
+#
+#= require jquery
+#= require jquery.ui.all
+#= require jquery_ujs
+#= require jquery.cookie
+#= require jquery.endless-scroll
+#= require jquery.highlight
+#= require jquery.history
+#= require jquery.waitforimages
+#= require jquery.atwho
+#= require jquery.scrollto
+#= require jquery.blockUI
+#= require turbolinks
+#= require jquery.turbolinks
+#= require bootstrap
+#= require select2
+#= require raphael
+#= require g.raphael-min
+#= require g.bar-min
+#= require branch-graph
+#= require highlightjs.min
+#= require ace/ace
+#= require d3
+#= require underscore
+#= require nprogress
+#= require nprogress-turbolinks
+#= require_tree .
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -44,19 +74,11 @@ window.linkify = (str) ->
window.simpleFormat = (str) ->
linkify(sanitize(str).replace(/\n/g, '
'))
-window.startSpinner = ->
- $('.turbolink-spinner').fadeIn()
-
-window.stopSpinner = ->
- $('.turbolink-spinner').fadeOut()
-
window.unbindEvents = ->
$(document).unbind('scroll')
$(document).off('scroll')
-document.addEventListener("page:fetch", startSpinner)
document.addEventListener("page:fetch", unbindEvents)
-document.addEventListener("page:receive", stopSpinner)
$ ->
# Click a .one_click_select field, select the contents
@@ -65,13 +87,8 @@ $ ->
$('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut()
- # Click a .appear-link, appear-data fadeout
- $(".appear-link").on 'click', (e) ->
- $('.appear-data').fadeIn()
- e.preventDefault()
-
- # Initialize chosen selects
- $('select.chosen').chosen()
+ # Initialize select2 selects
+ $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
# Initialize tooltips
$('.has_tooltip').tooltip()
@@ -84,6 +101,7 @@ $ ->
$(@).parents('form').submit()
$("abbr.timeago").timeago()
+ $('.js-timeago').timeago()
# Flash
if (flash = $(".flash-container")).length > 0
@@ -119,21 +137,11 @@ $ ->
# Commit show suppressed diff
- $(".content").on "click", ".supp_diff_link", ->
+ $(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
$(@).remove()
- $(".content").on "click", ".js-details-expand", ->
- $(@).next('.js-details-contain').removeClass("hide")
- $(@).remove()
-
(($) ->
- _chosen = $.fn.chosen
- $.fn.extend chosen: (options) ->
- default_options = search_contains: "true"
- $.extend default_options, options
- _chosen.apply @, [default_options]
-
# Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: ->
$(@).attr('disabled', 'disabled').addClass('disabled')
diff --git a/app/assets/javascripts/behaviors/details_behavior.coffee b/app/assets/javascripts/behaviors/details_behavior.coffee
index 7ad5c81894..decab3e1be 100644
--- a/app/assets/javascripts/behaviors/details_behavior.coffee
+++ b/app/assets/javascripts/behaviors/details_behavior.coffee
@@ -1,5 +1,15 @@
$ ->
$("body").on "click", ".js-details-target", ->
container = $(@).closest(".js-details-container")
-
container.toggleClass("open")
+
+ # Show details content. Hides link after click.
+ #
+ # %div
+ # %a.js-details-expand
+ # %div.js-details-content
+ #
+ $("body").on "click", ".js-details-expand", (e) ->
+ $(@).next('.js-details-content').removeClass("hide")
+ $(@).hide()
+ e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
index 7e438c51c1..d06cb116df 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -1,17 +1,18 @@
$ ->
$("body").on "click", ".js-toggler-target", ->
container = $(@).closest(".js-toggler-container")
-
container.toggleClass("on")
-
- $("body").on "click", ".js-toggle-visibility-link", (e) ->
+
+ # Toggle button. Show/hide content inside parent container.
+ # Button does not change visibility. If button has icon - it changes chevron style.
+ #
+ # %div.js-toggle-container
+ # %a.js-toggle-button
+ # %div.js-toggle-content
+ #
+ $("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
- container = $(".js-toggle-visibility-container")
- container.toggleClass("hide")
- e.preventDefault()
-
- $("body").on "click", ".js-toggle-button", (e) ->
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault()
diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee
index 03e280f897..584f6faea1 100644
--- a/app/assets/javascripts/blob.js.coffee
+++ b/app/assets/javascripts/blob.js.coffee
@@ -1,24 +1,76 @@
class BlobView
constructor: ->
+ # handle multi-line select
+ handleMultiSelect = (e) ->
+ [ first_line, last_line ] = parseSelectedLines()
+ [ line_number ] = parseSelectedLines($(this).attr("id"))
+ hash = "L#{line_number}"
+
+ if e.shiftKey and not isNaN(first_line) and not isNaN(line_number)
+ if line_number < first_line
+ last_line = first_line
+ first_line = line_number
+ else
+ last_line = line_number
+
+ hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}"
+
+ setHash(hash)
+ e.preventDefault()
+
# See if there are lines selected
# "#L12" and "#L34-56" supported
- highlightBlobLines = ->
- if window.location.hash isnt ""
- matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
+ highlightBlobLines = (e) ->
+ [ first_line, last_line ] = parseSelectedLines()
+
+ unless isNaN first_line
+ $("#tree-content-holder .highlight .line").removeClass("hll")
+ $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
+ $("#L#{first_line}").ScrollTo() unless e?
+
+ # parse selected lines from hash
+ # always return first and last line (initialized to NaN)
+ parseSelectedLines = (str) ->
+ first_line = NaN
+ last_line = NaN
+ hash = str || window.location.hash
+
+ if hash isnt ""
+ matches = hash.match(/\#?L(\d+)(\-(\d+))?/)
first_line = parseInt(matches?[1])
last_line = parseInt(matches?[3])
+ last_line = first_line if isNaN(last_line)
- unless isNaN first_line
- last_line = first_line if isNaN(last_line)
- $("#tree-content-holder .highlight .line").removeClass("hll")
- $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
- $("#L#{first_line}").ScrollTo()
+ [ first_line, last_line ]
+
+ setHash = (hash) ->
+ hash = hash.replace(/^\#/, "")
+ nodes = $("#" + hash)
+ # if any nodes are using this id, they must be temporarily changed
+ # also, add a temporary div at the top of the screen to prevent scrolling
+ if nodes.length > 0
+ scroll_top = $(document).scrollTop()
+ nodes.attr("id", "")
+ tmp = $("
")
+ .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" })
+ .attr("id", hash)
+ .appendTo(document.body)
+
+ window.location.hash = hash
+
+ # restore the nodes
+ if nodes.length > 0
+ tmp.remove()
+ nodes.attr("id", hash)
+
+ # initialize multi-line select
+ $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect)
# Highlight the correct lines on load
highlightBlobLines()
# Highlight the correct lines when the hash part of the URL changes
- $(window).on 'hashchange', highlightBlobLines
+ $(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView
diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee
index 318538509a..f6d57bd55b 100644
--- a/app/assets/javascripts/branch-graph.js.coffee
+++ b/app/assets/javascripts/branch-graph.js.coffee
@@ -194,11 +194,14 @@ class BranchGraph
fill: @colors[commit.space]
stroke: "none"
)
- r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr(
- fill: "url(#{commit.author.icon})"
+
+ avatar_box_x = @offsetX + @unitSpace * @mspace + 10
+ avatar_box_y = y - 10
+ r.rect(avatar_box_x, avatar_box_y, 20, 20).attr(
stroke: @colors[commit.space]
"stroke-width": 2
)
+ r.image(gon.relative_url_root + commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
"text-anchor": "start"
font: "14px Monaco, monospace"
@@ -271,7 +274,7 @@ class BranchGraph
Raphael::commitTooltip = (x, y, commit) ->
boxWidth = 300
boxHeight = 200
- icon = @image(commit.author.icon, x, y, 20, 20)
+ icon = @image(gon.relative_url_root + commit.author.icon, x, y, 20, 20)
nameText = @text(x + 25, y + 10, commit.author.name)
idText = @text(x, y + 35, commit.id)
messageText = @text(x, y + 50, commit.message)
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
index 9f55a1e636..5f53439ca4 100644
--- a/app/assets/javascripts/commit.js.coffee
+++ b/app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,6 @@
class Commit
constructor: ->
- $('.files .file').each ->
+ $('.files .diff-file').each ->
new CommitFile(this)
@Commit = Commit
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
index a45ee58d1b..4db9116a9d 100644
--- a/app/assets/javascripts/commit/file.js.coffee
+++ b/app/assets/javascripts/commit/file.js.coffee
@@ -1,7 +1,7 @@
class CommitFile
-
+
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
-
-this.CommitFile = CommitFile
\ No newline at end of file
+
+@CommitFile = CommitFile
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
index f901a9e28f..607b85eb45 100644
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ b/app/assets/javascripts/commit/image-file.js.coffee
@@ -125,4 +125,4 @@ class ImageFile
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
-this.ImageFile = ImageFile
\ No newline at end of file
+@ImageFile = ImageFile
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index de4c06a272..9c004c997e 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -4,13 +4,13 @@ class CommitsList
limit: 0
offset: 0
@disable = false
-
+
@showProgress: ->
$('.loading').show()
-
+
@hideProgress: ->
$('.loading').hide()
-
+
@init: (ref, limit) ->
$(".day-commits-table li.commit").live 'click', (event) ->
if event.target.nodeName != "A"
@@ -21,7 +21,7 @@ class CommitsList
@data.ref = ref
@data.limit = limit
@data.offset = limit
-
+
this.initLoadMore()
this.showProgress()
@@ -32,7 +32,9 @@ class CommitsList
url: location.href
data: @data
complete: this.hideProgress
- dataType: "script"
+ success: (data) ->
+ CommitsList.append(data.count, data.html)
+ dataType: "json"
@append: (count, html) ->
$("#commits-list").append(html)
@@ -40,7 +42,7 @@ class CommitsList
@data.offset += count
else
@disable = true
-
+
@initLoadMore: ->
$(document).unbind('scroll')
$(document).endlessScroll
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index e264e28130..b61d9875e0 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -4,6 +4,7 @@ $ ->
class Dispatcher
constructor: () ->
@initSearch()
+ @initHighlight()
@initPageScripts()
initPageScripts: ->
@@ -18,6 +19,8 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
+ when 'projects:issues:show'
+ new Issue()
when 'projects:issues:new', 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
when 'dashboard:show'
@@ -47,5 +50,16 @@ class Dispatcher
initSearch: ->
- autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts')
- new SearchAutocomplete(autocomplete_json)
+ opts = $('.search-autocomplete-opts')
+ path = opts.data('autocomplete-path')
+ project_id = opts.data('autocomplete-project-id')
+ project_ref = opts.data('autocomplete-project-ref')
+
+ new SearchAutocomplete(path, project_id, project_ref)
+
+ initHighlight: ->
+ $('.highlight pre code').each (i, e) ->
+ $(e).html($.map($(e).html().split("\n"), (line, i) ->
+ "" + line + ""
+ ).join("\n"))
+ hljs.highlightBlock(e)
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 77091da8f6..00d56ae5b4 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -6,13 +6,13 @@ GitLab.GfmAutoComplete =
dataSource: ''
# Emoji
Emoji:
- assetBase: ''
template: '${name}
'
# Team Members
Members:
template: '${username} ${name}'
+ # Issues and MergeRequests
Issues:
template: '${id} ${title} '
@@ -26,7 +26,7 @@ GitLab.GfmAutoComplete =
tpl: @Emoji.template
callbacks:
before_save: (emojis) =>
- $.map emojis, (em) => name: em, insert: em+ ':', image: "#{@Emoji.assetBase}/#{em}.png"
+ $.map emojis, (em) => name: em.name, insert: em.name+ ':', image: em.path
# Team Members
input.atwho
@@ -46,11 +46,22 @@ GitLab.GfmAutoComplete =
before_save: (issues) ->
$.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ input.atwho
+ at: '!'
+ alias: 'mergerequests'
+ search_key: 'search'
+ tpl: @Issues.template
+ callbacks:
+ before_save: (merges) ->
+ $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
+
input.one "focus", =>
$.getJSON(@dataSource).done (data) ->
# load members
input.atwho 'load', "@", data.members
# load issues
input.atwho 'load', "issues", data.issues
+ # load merge requests
+ input.atwho 'load', "mergerequests", data.mergerequests
# load emojis
input.atwho 'load', ":", data.emojis
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
index c0ffccd8f7..7850eb14e7 100644
--- a/app/assets/javascripts/groups.js.coffee
+++ b/app/assets/javascripts/groups.js.coffee
@@ -4,3 +4,14 @@ class GroupMembers
$(this).fadeOut()
@GroupMembers = GroupMembers
+
+$ ->
+ # avatar
+ $('.js-choose-group-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-group-avatar-input").click()
+
+ $('.js-group-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
\ No newline at end of file
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
new file mode 100644
index 0000000000..36935a0a15
--- /dev/null
+++ b/app/assets/javascripts/issue.js.coffee
@@ -0,0 +1,9 @@
+class Issue
+ constructor: ->
+ $('.edit-issue.inline-update input[type="submit"]').hide()
+ $(".issue-box .inline-update").on "change", "select", ->
+ $(this).submit()
+ $(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
+ $(this).submit()
+
+@Issue = Issue
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 67d9498c50..e2f1bc743f 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -22,19 +22,17 @@
backgroundColor: '#DDD'
opacity: .4
)
-
+
reload: ->
Issues.initSelects()
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
- $("#update_status").chosen()
- $("#update_assignee_id").chosen()
- $("#update_milestone_id").chosen()
- $("#label_name").chosen()
- $("#assignee_id").chosen()
- $("#milestone_id").chosen()
+ $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
+ $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
+ $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
+ $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
$("#milestone_id, #assignee_id, #label_name").on "change", ->
$(this).closest("form").submit()
@@ -54,7 +52,16 @@
unless terms is last_terms
last_terms = terms
if terms.length >= 2 or terms.length is 0
- form.submit()
+ $.ajax
+ type: "GET"
+ url: location.href
+ data: "issue_search=" + terms
+ complete: ->
+ $(".loading").hide()
+ success: (data) ->
+ $('.issues-holder').html(data.html)
+ Issues.reload()
+ dataType: "json"
checkChanged: ->
checked_issues = $(".selected_issue:checked")
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
new file mode 100644
index 0000000000..7589fc70cd
--- /dev/null
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -0,0 +1,129 @@
+class MergeRequest
+ constructor: (@opts) ->
+ @initContextWidget()
+ this.$el = $('.merge-request')
+ @diffs_loaded = if @opts.action == 'diffs' then true else false
+ @commits_loaded = false
+
+ this.activateTab(@opts.action)
+
+ this.bindEvents()
+
+ this.initMergeWidget()
+ this.$('.show-all-commits').on 'click', =>
+ this.showAllCommits()
+
+ modal = $('#modal_merge_info').modal(show: false)
+
+ disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request'
+
+
+ # Local jQuery finder
+ $: (selector) ->
+ this.$el.find(selector)
+
+ initContextWidget: ->
+ $('.edit-merge_request.inline-update input[type="submit"]').hide()
+ $(".issue-box .inline-update").on "change", "select", ->
+ $(this).submit()
+ $(".issue-box .inline-update").on "change", "#merge_request_assignee_id", ->
+ $(this).submit()
+
+ initMergeWidget: ->
+ this.showState( @opts.current_status )
+
+ if this.$('.automerge_widget').length and @opts.check_enable
+ $.get @opts.url_to_automerge_check, (data) =>
+ this.showState( data.merge_status )
+ , 'json'
+
+ if @opts.ci_enable
+ $.get @opts.url_to_ci_check, (data) =>
+ this.showCiState data.status
+ , 'json'
+
+ bindEvents: ->
+ this.$('.nav-tabs').on 'click', 'a', (event) =>
+ a = $(event.currentTarget)
+
+ href = a.attr('href')
+ History.replaceState {path: href}, document.title, href
+
+ event.preventDefault()
+
+ this.$('.nav-tabs').on 'click', 'li', (event) =>
+ this.activateTab($(event.currentTarget).data('action'))
+
+ this.$('.accept_merge_request').on 'click', ->
+ $('.automerge_widget.can_be_merged').hide()
+ $('.merge-in-progress').show()
+
+ this.$('.remove_source_branch').on 'click', ->
+ $('.remove_source_branch_widget').hide()
+ $('.remove_source_branch_in_progress').show()
+
+ this.$(".remove_source_branch").on "ajax:success", (e, data, status, xhr) ->
+ location.reload()
+
+ this.$(".remove_source_branch").on "ajax:error", (e, data, status, xhr) =>
+ this.$('.remove_source_branch_widget').hide()
+ this.$('.remove_source_branch_in_progress').hide()
+ this.$('.remove_source_branch_widget.failed').show()
+
+ activateTab: (action) ->
+ this.$('.nav-tabs li').removeClass 'active'
+ this.$('.tab-content').hide()
+ switch action
+ when 'diffs'
+ this.$('.nav-tabs .diffs-tab').addClass 'active'
+ this.loadDiff() unless @diffs_loaded
+ this.$('.diffs').show()
+ else
+ this.$('.nav-tabs .notes-tab').addClass 'active'
+ this.$('.notes').show()
+
+ showState: (state) ->
+ $('.automerge_widget').hide()
+ $('.automerge_widget.' + state).show()
+
+ showCiState: (state) ->
+ $('.ci_widget').hide()
+ allowed_states = ["failed", "running", "pending", "success"]
+ if state in allowed_states
+ $('.ci_widget.ci-' + state).show()
+ else
+ $('.ci_widget.ci-error').show()
+
+ switch state
+ when "success"
+ $('.mr-state-widget').addClass("panel-success")
+ when "failed"
+ $('.mr-state-widget').addClass("panel-danger")
+ when "running", "pending"
+ $('.mr-state-widget').addClass("panel-warning")
+
+
+
+ loadDiff: (event) ->
+ $.ajax
+ type: 'GET'
+ url: this.$('.nav-tabs .diffs-tab a').attr('href')
+ beforeSend: =>
+ this.$('.status').addClass 'loading'
+ complete: =>
+ @diffs_loaded = true
+ this.$('.status').removeClass 'loading'
+ success: (data) =>
+ this.$(".diffs").html(data.html)
+ dataType: 'json'
+
+ showAllCommits: ->
+ this.$('.first-commits').remove()
+ this.$('.all-commits').removeClass 'hide'
+
+ alreadyOrCannotBeMerged: ->
+ this.$('.automerge_widget').hide()
+ this.$('.merge-in-progress').hide()
+ this.$('.automerge_widget.already_cannot_be_merged').show()
+
+this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
index 5400bc5c1a..9201c84c5e 100644
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ b/app/assets/javascripts/merge_requests.js.coffee
@@ -2,101 +2,7 @@
# * Filter merge requests
#
@merge_requestsPage = ->
- $('#assignee_id').chosen()
- $('#milestone_id').chosen()
+ $('#assignee_id').select2()
+ $('#milestone_id').select2()
$('#milestone_id, #assignee_id').on 'change', ->
$(this).closest('form').submit()
-
-class MergeRequest
-
- constructor: (@opts) ->
- this.$el = $('.merge-request')
- @diffs_loaded = if @opts.action == 'diffs' then true else false
- @commits_loaded = false
-
- this.activateTab(@opts.action)
-
- this.bindEvents()
-
- this.initMergeWidget()
- this.$('.show-all-commits').on 'click', =>
- this.showAllCommits()
-
- modal = $('#modal_merge_info').modal(show: false)
-
- # Local jQuery finder
- $: (selector) ->
- this.$el.find(selector)
-
- initMergeWidget: ->
- this.showState( @opts.current_status )
-
- if this.$('.automerge_widget').length and @opts.check_enable
- $.get @opts.url_to_automerge_check, (data) =>
- this.showState( data.merge_status )
- , 'json'
-
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
- , 'json'
-
- bindEvents: ->
- this.$('.nav-tabs').on 'click', 'a', (event) =>
- a = $(event.currentTarget)
-
- href = a.attr('href')
- History.replaceState {path: href}, document.title, href
-
- event.preventDefault()
-
- this.$('.nav-tabs').on 'click', 'li', (event) =>
- this.activateTab($(event.currentTarget).data('action'))
-
- this.$('.accept_merge_request').on 'click', ->
- $('.automerge_widget.can_be_merged').hide()
- $('.merge-in-progress').show()
-
- activateTab: (action) ->
- this.$('.nav-tabs li').removeClass 'active'
- this.$('.tab-content').hide()
- switch action
- when 'diffs'
- this.$('.nav-tabs .diffs-tab').addClass 'active'
- this.loadDiff() unless @diffs_loaded
- this.$('.diffs').show()
- else
- this.$('.nav-tabs .notes-tab').addClass 'active'
- this.$('.notes').show()
-
- showState: (state) ->
- $('.automerge_widget').hide()
- $('.automerge_widget.' + state).show()
-
- showCiState: (state) ->
- $('.ci_widget').hide()
- $('.ci_widget.ci-' + state).show()
-
- loadDiff: (event) ->
- $.ajax
- type: 'GET'
- url: this.$('.nav-tabs .diffs-tab a').attr('href')
- beforeSend: =>
- this.$('.status').addClass 'loading'
-
- complete: =>
- @diffs_loaded = true
- this.$('.status').removeClass 'loading'
-
- dataType: 'script'
-
- showAllCommits: ->
- this.$('.first-commits').remove()
- this.$('.all-commits').removeClass 'hide'
-
- alreadyOrCannotBeMerged: ->
- this.$('.automerge_widget').hide()
- this.$('.merge-in-progress').hide()
- this.$('.automerge_widget.already_cannot_be_merged').show()
-
-this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee
new file mode 100644
index 0000000000..00d135d144
--- /dev/null
+++ b/app/assets/javascripts/namespace_select.js.coffee
@@ -0,0 +1,24 @@
+$ ->
+ namespaceFormatResult = (namespace) ->
+ markup = ""
+ markup += "" + namespace.kind + ""
+ markup += "" + namespace.path + ""
+ markup += "
"
+ markup
+
+ formatSelection = (namespace) ->
+ namespace.kind + ": " + namespace.path
+
+ $('.ajax-namespace-select').each (i, select) ->
+ $(select).select2
+ placeholder: "Search for namespace"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.namespaces query.term, (namespaces) ->
+ data = { results: namespaces }
+ query.callback(data)
+
+ dropdownCssClass: "ajax-namespace-dropdown"
+ formatResult: namespaceFormatResult
+ formatSelection: formatSelection
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
deleted file mode 100644
index 5225623c1f..0000000000
--- a/app/assets/javascripts/notes.js
+++ /dev/null
@@ -1,580 +0,0 @@
-var NoteList = {
- id: null,
- notes_path: null,
- target_params: null,
- target_id: 0,
- target_type: null,
-
- init: function(tid, tt, path) {
- NoteList.notes_path = path + ".js";
- NoteList.target_id = tid;
- NoteList.target_type = tt;
- NoteList.target_params = "target_type=" + NoteList.target_type + "&target_id=" + NoteList.target_id;
-
- NoteList.setupMainTargetNoteForm();
-
- // get initial set of notes
- NoteList.getContent();
-
- // Unbind events to prevent firing twice
- $(document).off("click", ".js-add-diff-note-button");
- $(document).off("click", ".js-discussion-reply-button");
- $(document).off("click", ".js-note-preview-button");
- $(document).off("click", ".js-note-attachment-input");
- $(document).off("click", ".js-close-discussion-note-form");
- $(document).off("click", ".js-note-delete");
- $(document).off("click", ".js-note-edit");
- $(document).off("click", ".js-note-edit-cancel");
- $(document).off("click", ".js-note-attachment-delete");
- $(document).off("click", ".js-choose-note-attachment-button");
- $(document).off("click", ".js-show-outdated-discussion");
-
- $(document).off("ajax:complete", ".js-main-target-form");
-
-
- // add a new diff note
- $(document).on("click",
- ".js-add-diff-note-button",
- NoteList.addDiffNote);
-
- // reply to diff/discussion notes
- $(document).on("click",
- ".js-discussion-reply-button",
- NoteList.replyToDiscussionNote);
-
- // setup note preview
- $(document).on("click",
- ".js-note-preview-button",
- NoteList.previewNote);
-
- // update the file name when an attachment is selected
- $(document).on("change",
- ".js-note-attachment-input",
- NoteList.updateFormAttachment);
-
- // hide diff note form
- $(document).on("click",
- ".js-close-discussion-note-form",
- NoteList.removeDiscussionNoteForm);
-
- // remove a note (in general)
- $(document).on("click",
- ".js-note-delete",
- NoteList.removeNote);
-
- // show the edit note form
- $(document).on("click",
- ".js-note-edit",
- NoteList.showEditNoteForm);
-
- // cancel note editing
- $(document).on("click",
- ".note-edit-cancel",
- NoteList.cancelNoteEdit);
-
- // delete note attachment
- $(document).on("click",
- ".js-note-attachment-delete",
- NoteList.deleteNoteAttachment);
-
- // update the note after editing
- $(document).on("ajax:complete",
- "form.edit_note",
- NoteList.updateNote);
-
- // reset main target form after submit
- $(document).on("ajax:complete",
- ".js-main-target-form",
- NoteList.resetMainTargetForm);
-
-
- $(document).on("click",
- ".js-choose-note-attachment-button",
- NoteList.chooseNoteAttachment);
-
- $(document).on("click",
- ".js-show-outdated-discussion",
- function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() });
- },
-
-
- /**
- * When clicking on buttons
- */
-
- /**
- * Called when clicking on the "add a comment" button on the side of a diff line.
- *
- * Inserts a temporary row for the form below the line.
- * Sets up the form and shows it.
- */
- addDiffNote: function(e) {
- e.preventDefault();
-
- // find the form
- var form = $(".js-new-note-form");
- var row = $(this).closest("tr");
- var nextRow = row.next();
-
- // does it already have notes?
- if (nextRow.is(".notes_holder")) {
- $.proxy(NoteList.replyToDiscussionNote,
- nextRow.find(".js-discussion-reply-button")
- ).call();
- } else {
- // add a notes row and insert the form
- row.after(' | |
');
- form.clone().appendTo(row.next().find(".notes_content"));
-
- // show the form
- NoteList.setupDiscussionNoteForm($(this), row.next().find("form"));
- }
- },
-
- /**
- * Called when clicking the "Choose File" button.
- *
- * Opens the file selection dialog.
- */
- chooseNoteAttachment: function() {
- var form = $(this).closest("form");
-
- form.find(".js-note-attachment-input").click();
- },
-
- /**
- * Shows the note preview.
- *
- * Lets the server render GFM into Html and displays it.
- *
- * Note: uses the Toggler behavior to toggle preview/edit views/buttons
- */
- previewNote: function(e) {
- e.preventDefault();
-
- var form = $(this).closest("form");
- var preview = form.find('.js-note-preview');
- var noteText = form.find('.js-note-text').val();
-
- if(noteText.trim().length === 0) {
- preview.text('Nothing to preview.');
- } else {
- preview.text('Loading...');
- $.post($(this).data('url'), {note: noteText})
- .success(function(previewData) {
- preview.html(previewData);
- });
- }
- },
-
- /**
- * Called in response to "cancel" on a diff note form.
- *
- * Shows the reply button again.
- * Removes the form and if necessary it's temporary row.
- */
- removeDiscussionNoteForm: function() {
- var form = $(this).closest("form");
- var row = form.closest("tr");
-
- // show the reply button (will only work for replies)
- form.prev(".js-discussion-reply-button").show();
-
- if (row.is(".js-temp-notes-holder")) {
- // remove temporary row for diff lines
- row.remove();
- } else {
- // only remove the form
- form.remove();
- }
- },
-
- /**
- * Called in response to deleting a note of any kind.
- *
- * Removes the actual note from view.
- * Removes the whole discussion if the last note is being removed.
- */
- removeNote: function() {
- var note = $(this).closest(".note");
- var notes = note.closest(".notes");
-
- // check if this is the last note for this line
- if (notes.find(".note").length === 1) {
- // for discussions
- notes.closest(".discussion").remove();
-
- // for diff lines
- notes.closest("tr").remove();
- }
-
- note.remove();
- NoteList.updateVotes();
- },
-
- /**
- * Called in response to clicking the edit note link
- *
- * Replaces the note text with the note edit form
- * Adds a hidden div with the original content of the note to fill the edit note form with
- * if the user cancels
- */
- showEditNoteForm: function(e) {
- e.preventDefault();
- var note = $(this).closest(".note");
- note.find(".note-text").hide();
-
- // Show the attachment delete link
- note.find(".js-note-attachment-delete").show();
-
- GitLab.GfmAutoComplete.setup();
-
- var form = note.find(".note-edit-form");
- form.show();
-
- var textarea = form.find("textarea");
- var p = $("").text(textarea.val());
- var hidden_div = $('').append(p);
- form.append(hidden_div);
- hidden_div.hide();
- textarea.focus();
- },
-
- /**
- * Called in response to clicking the cancel button when editing a note
- *
- * Resets and hides the note editing form
- */
- cancelNoteEdit: function(e) {
- e.preventDefault();
- var note = $(this).closest(".note");
- NoteList.resetNoteEditing(note);
- },
-
-
- /**
- * Called in response to clicking the delete attachment link
- *
- * Removes the attachment wrapper view, including image tag if it exists
- * Resets the note editing form
- */
- deleteNoteAttachment: function() {
- var note = $(this).closest(".note");
- note.find(".note-attachment").remove();
- NoteList.resetNoteEditing(note);
- NoteList.rewriteTimestamp(note.find(".note-last-update"));
- },
-
-
- /**
- * Called when clicking on the "reply" button for a diff line.
- *
- * Shows the note form below the notes.
- */
- replyToDiscussionNote: function() {
- // find the form
- var form = $(".js-new-note-form");
-
- // hide reply button
- $(this).hide();
- // insert the form after the button
- form.clone().insertAfter($(this));
-
- // show the form
- NoteList.setupDiscussionNoteForm($(this), $(this).next("form"));
- },
-
-
- /**
- * Helper for inserting and setting up note forms.
- */
-
-
- /**
- * Called in response to creating a note failing validation.
- *
- * Adds the rendered errors to the respective form.
- * If "discussionId" is null or undefined, the main target form is assumed.
- */
- errorsOnForm: function(errorsHtml, discussionId) {
- // find the form
- if (discussionId) {
- var form = $("form[rel='"+discussionId+"']");
- } else {
- var form = $(".js-main-target-form");
- }
-
- form.find(".js-errors").remove();
- form.prepend(errorsHtml);
-
- form.find(".js-note-text").focus();
- },
-
-
- /**
- * Shows the diff/discussion form and does some setup on it.
- *
- * Sets some hidden fields in the form.
- *
- * Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
- * and "noteableId" data attributes set.
- */
- setupDiscussionNoteForm: function(dataHolder, form) {
- // setup note target
- form.attr("rel", dataHolder.data("discussionId"));
- form.find("#note_commit_id").val(dataHolder.data("commitId"));
- form.find("#note_line_code").val(dataHolder.data("lineCode"));
- form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
- form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
-
- NoteList.setupNoteForm(form);
-
- form.find(".js-note-text").focus();
- },
-
- /**
- * Shows the main form and does some setup on it.
- *
- * Sets some hidden fields in the form.
- */
- setupMainTargetNoteForm: function() {
- // find the form
- var form = $(".js-new-note-form");
- // insert the form after the button
- form.clone().replaceAll($(".js-main-target-form"));
-
- form = form.prev("form");
-
- // show the form
- NoteList.setupNoteForm(form);
-
- // fix classes
- form.removeClass("js-new-note-form");
- form.addClass("js-main-target-form");
-
- // remove unnecessary fields and buttons
- form.find("#note_line_code").remove();
- form.find(".js-close-discussion-note-form").remove();
- },
-
- /**
- * General note form setup.
- *
- * * deactivates the submit button when text is empty
- * * hides the preview button when text is empty
- * * setup GFM auto complete
- * * show the form
- */
- setupNoteForm: function(form) {
- disableButtonIfEmptyField(form.find(".js-note-text"), form.find(".js-comment-button"));
-
- form.removeClass("js-new-note-form");
-
- // setup preview buttons
- form.find(".js-note-edit-button, .js-note-preview-button")
- .tooltip({ placement: 'left' });
-
- previewButton = form.find(".js-note-preview-button");
- form.find(".js-note-text").on("input", function() {
- if ($(this).val().trim() !== "") {
- previewButton.removeClass("turn-off").addClass("turn-on");
- } else {
- previewButton.removeClass("turn-on").addClass("turn-off");
- }
- });
-
- // remove notify commit author checkbox for non-commit notes
- if (form.find("#note_noteable_type").val() !== "Commit") {
- form.find(".js-notify-commit-author").remove();
- }
-
- GitLab.GfmAutoComplete.setup();
-
- form.show();
- },
-
-
- /**
- * Handle loading the initial set of notes.
- * And set up loading more notes when scrolling to the bottom of the page.
- */
-
-
- /**
- * Gets an initial set of notes.
- */
- getContent: function() {
- $.ajax({
- url: NoteList.notes_path,
- data: NoteList.target_params,
- complete: function(){ $('.js-notes-busy').removeClass("loading")},
- beforeSend: function() { $('.js-notes-busy').addClass("loading") },
- dataType: "script"
- });
- },
-
- /**
- * Called in response to getContent().
- * Replaces the content of #notes-list with the given html.
- */
- setContent: function(newNoteIds, html) {
- $("#notes-list").html(html);
- },
-
-
- /**
- * Adds a single common note to #notes-list.
- */
- appendNewNote: function(id, html) {
- $("#notes-list").append(html);
- NoteList.updateVotes();
- },
-
- /**
- * Adds a single discussion note to #notes-list.
- *
- * Also removes the corresponding form.
- */
- appendNewDiscussionNote: function(discussionId, diffRowHtml, noteHtml) {
- var form = $("form[rel='"+discussionId+"']");
- var row = form.closest("tr");
-
- // is this the first note of discussion?
- if (row.is(".js-temp-notes-holder")) {
- // insert the note and the reply button after the temp row
- row.after(diffRowHtml);
- // remove the note (will be added again below)
- row.next().find(".note").remove();
- }
-
- // append new note to all matching discussions
- $(".notes[rel='"+discussionId+"']").append(noteHtml);
-
- // cleanup after successfully creating a diff/discussion note
- $.proxy(NoteList.removeDiscussionNoteForm, form).call();
- },
-
- /**
- * Called in response the main target form has been successfully submitted.
- *
- * Removes any errors.
- * Resets text and preview.
- * Resets buttons.
- */
- resetMainTargetForm: function(){
- var form = $(this);
-
- // remove validation errors
- form.find(".js-errors").remove();
-
- // reset text and preview
- var previewContainer = form.find(".js-toggler-container.note_text_and_preview");
- if (previewContainer.is(".on")) {
- previewContainer.removeClass("on");
- }
- form.find(".js-note-text").val("").trigger("input");
- },
-
- /**
- * Called after an attachment file has been selected.
- *
- * Updates the file name for the selected attachment.
- */
- updateFormAttachment: function() {
- var form = $(this).closest("form");
-
- // get only the basename
- var filename = $(this).val().replace(/^.*[\\\/]/, '');
-
- form.find(".js-attachment-filename").text(filename);
- },
-
- /**
- * Recalculates the votes and updates them (if they are displayed at all).
- *
- * Assumes all relevant notes are displayed (i.e. there are no more notes to
- * load via getMore()).
- * Might produce inaccurate results when not all notes have been loaded and a
- * recalculation is triggered (e.g. when deleting a note).
- */
- updateVotes: function() {
- var votes = $("#votes .votes");
- var notes = $("#notes-list .note .vote");
-
- // only update if there is a vote display
- if (votes.size()) {
- var upvotes = notes.filter(".upvote").size();
- var downvotes = notes.filter(".downvote").size();
- var votesCount = upvotes + downvotes;
- var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
- var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;
-
- // change vote bar lengths
- votes.find(".bar-success").css("width", upvotesPercent+"%");
- votes.find(".bar-danger").css("width", downvotesPercent+"%");
- // replace vote numbers
- votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
- votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
- }
- },
-
- /**
- * Called in response to the edit note form being submitted
- *
- * Updates the current note field.
- * Hides the edit note form
- */
- updateNote: function(e, xhr, settings) {
- response = JSON.parse(xhr.responseText);
- if (response.success) {
- var note_li = $("#note_" + response.id);
- var note_text = note_li.find(".note-text");
- note_text.html(response.note).show();
-
- var note_form = note_li.find(".note-edit-form");
- note_form.hide();
- note_form.find(".btn-save").enableButton();
-
- // Update the "Edited at xxx label" on the note to show it's just been updated
- NoteList.rewriteTimestamp(note_li.find(".note-last-update"));
- }
- },
-
- /**
- * Called in response to the 'cancel note' link clicked, or after deleting a note attachment
- *
- * Hides the edit note form and shows the note
- * Resets the edit note form textarea with the original content of the note
- */
- resetNoteEditing: function(note) {
- note.find(".note-text").show();
-
- // Hide the attachment delete link
- note.find(".js-note-attachment-delete").hide();
-
- // Put the original content of the note back into the edit form textarea
- var form = note.find(".note-edit-form");
- var original_content = form.find(".note-original-content");
- form.find("textarea").val(original_content.text());
- original_content.remove();
-
- note.find(".note-edit-form").hide();
- },
-
- /**
- * Utility function to generate new timestamp text for a note
- *
- */
- rewriteTimestamp: function(element) {
- // Strip all newlines from the existing timestamp
- var ts = element.text().replace(/\n/g, ' ').trim();
-
- // If the timestamp already has '(Edited xxx ago)' text, remove it
- ts = ts.replace(new RegExp("\\(Edited [A-Za-z0-9 ]+\\)$", "gi"), "");
-
- // Append "(Edited just now)"
- ts = (ts + " (Edited just now)");
-
- element.html(ts);
- }
-};
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
new file mode 100644
index 0000000000..4510718c2f
--- /dev/null
+++ b/app/assets/javascripts/notes.js.coffee
@@ -0,0 +1,473 @@
+class Notes
+ @interval: null
+
+ constructor: (notes_url, note_ids, last_fetched_at) ->
+ @notes_url = notes_url
+ @notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
+ @note_ids = note_ids
+ @last_fetched_at = last_fetched_at
+ @initRefresh()
+ @setupMainTargetNoteForm()
+ @cleanBinding()
+ @addBinding()
+
+ addBinding: ->
+ # add note to UI after creation
+ $(document).on "ajax:success", ".js-main-target-form", @addNote
+ $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
+
+ # change note in UI after update
+ $(document).on "ajax:success", "form.edit_note", @updateNote
+
+ # Edit note link
+ $(document).on "click", ".js-note-edit", @showEditForm
+ $(document).on "click", ".note-edit-cancel", @cancelEdit
+
+ # remove a note (in general)
+ $(document).on "click", ".js-note-delete", @removeNote
+
+ # delete note attachment
+ $(document).on "click", ".js-note-attachment-delete", @removeAttachment
+
+ # Preview button
+ $(document).on "click", ".js-note-preview-button", @previewNote
+
+ # reset main target form after submit
+ $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm
+
+ # attachment button
+ $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment
+
+ # update the file name when an attachment is selected
+ $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
+
+ # reply to diff/discussion notes
+ $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote
+
+ # add diff note
+ $(document).on "click", ".js-add-diff-note-button", @addDiffNote
+
+ # hide diff note form
+ $(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm
+
+ # fetch notes when tab becomes visible
+ $(document).on "visibilitychange", @visibilityChange
+
+ @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
+ $(document).on('keypress', @notes_forms, (e)->
+ if e.keyCode == 10 || (e.ctrlKey && e.keyCode == 13)
+ $(@).parents('form').submit()
+ )
+
+ cleanBinding: ->
+ $(document).off "ajax:success", ".js-main-target-form"
+ $(document).off "ajax:success", ".js-discussion-note-form"
+ $(document).off "ajax:success", "form.edit_note"
+ $(document).off "click", ".js-note-edit"
+ $(document).off "click", ".note-edit-cancel"
+ $(document).off "click", ".js-note-delete"
+ $(document).off "click", ".js-note-attachment-delete"
+ $(document).off "click", ".js-note-preview-button"
+ $(document).off "ajax:complete", ".js-main-target-form"
+ $(document).off "click", ".js-choose-note-attachment-button"
+ $(document).off "click", ".js-discussion-reply-button"
+ $(document).off "click", ".js-add-diff-note-button"
+ $(document).off "visibilitychange"
+ $(document).off "keypress", @notes_forms
+
+
+ initRefresh: ->
+ clearInterval(Notes.interval)
+ Notes.interval = setInterval =>
+ @refresh()
+ , 15000
+
+ refresh: ->
+ @getContent() unless document.hidden
+
+ getContent: ->
+ $.ajax
+ url: @notes_url
+ data: "last_fetched_at=" + @last_fetched_at
+ dataType: "json"
+ success: (data) =>
+ notes = data.notes
+ @last_fetched_at = data.last_fetched_at
+ $.each notes, (i, note) =>
+ @renderNote(note)
+
+
+ ###
+ Render note in main comments area.
+
+ Note: for rendering inline notes use renderDiscussionNote
+ ###
+ renderNote: (note) ->
+ # render note if it not present in loaded list
+ # or skip if rendered
+ if @isNewNote(note)
+ @note_ids.push(note.id)
+ $('ul.main-notes-list').append(note.html)
+ code = "#note_" + note.id + " .highlight pre code"
+ $(code).each (i, e) ->
+ hljs.highlightBlock(e)
+
+
+ ###
+ Check if note does not exists on page
+ ###
+ isNewNote: (note) ->
+ $.inArray(note.id, @note_ids) == -1
+
+
+ ###
+ Render note in discussion area.
+
+ Note: for rendering inline notes use renderDiscussionNote
+ ###
+ renderDiscussionNote: (note) ->
+ @note_ids.push(note.id)
+ form = $("form[rel='" + note.discussion_id + "']")
+ row = form.closest("tr")
+
+ # is this the first note of discussion?
+ if row.is(".js-temp-notes-holder")
+ # insert the note and the reply button after the temp row
+ row.after note.discussion_html
+
+ # remove the note (will be added again below)
+ row.next().find(".note").remove()
+
+ # append new note to all matching discussions
+ $(".notes[rel='" + note.discussion_id + "']").append note.html
+
+ # cleanup after successfully creating a diff/discussion note
+ @removeDiscussionNoteForm(form)
+
+ ###
+ Shows the note preview.
+
+ Lets the server render GFM into Html and displays it.
+
+ Note: uses the Toggler behavior to toggle preview/edit views/buttons
+ ###
+ previewNote: (e) ->
+ e.preventDefault()
+ form = $(this).closest("form")
+ preview = form.find(".js-note-preview")
+ noteText = form.find(".js-note-text").val()
+ if noteText.trim().length is 0
+ preview.text "Nothing to preview."
+ else
+ preview.text "Loading..."
+ $.post($(this).data("url"),
+ note: noteText
+ ).success (previewData) ->
+ preview.html previewData
+
+ ###
+ Called in response the main target form has been successfully submitted.
+
+ Removes any errors.
+ Resets text and preview.
+ Resets buttons.
+ ###
+ resetMainTargetForm: ->
+ form = $(".js-main-target-form")
+
+ # remove validation errors
+ form.find(".js-errors").remove()
+
+ # reset text and preview
+ previewContainer = form.find(".js-toggler-container.note_text_and_preview")
+ previewContainer.removeClass "on" if previewContainer.is(".on")
+ form.find(".js-note-text").val("").trigger "input"
+
+ ###
+ Called when clicking the "Choose File" button.
+
+ Opens the file selection dialog.
+ ###
+ chooseNoteAttachment: ->
+ form = $(this).closest("form")
+ form.find(".js-note-attachment-input").click()
+
+ ###
+ Shows the main form and does some setup on it.
+
+ Sets some hidden fields in the form.
+ ###
+ setupMainTargetNoteForm: ->
+
+ # find the form
+ form = $(".js-new-note-form")
+
+ # insert the form after the button
+ form.clone().replaceAll $(".js-main-target-form")
+ form = form.prev("form")
+
+ # show the form
+ @setupNoteForm(form)
+
+ # fix classes
+ form.removeClass "js-new-note-form"
+ form.addClass "js-main-target-form"
+
+ # remove unnecessary fields and buttons
+ form.find("#note_line_code").remove()
+ form.find(".js-close-discussion-note-form").remove()
+
+ ###
+ General note form setup.
+
+ deactivates the submit button when text is empty
+ hides the preview button when text is empty
+ setup GFM auto complete
+ show the form
+ ###
+ setupNoteForm: (form) ->
+ disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
+ form.removeClass "js-new-note-form"
+
+ # setup preview buttons
+ form.find(".js-note-edit-button, .js-note-preview-button").tooltip placement: "left"
+ previewButton = form.find(".js-note-preview-button")
+ form.find(".js-note-text").on "input", ->
+ if $(this).val().trim() isnt ""
+ previewButton.removeClass("turn-off").addClass "turn-on"
+ else
+ previewButton.removeClass("turn-on").addClass "turn-off"
+
+
+ # remove notify commit author checkbox for non-commit notes
+ form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit"
+ GitLab.GfmAutoComplete.setup()
+ form.show()
+
+
+ ###
+ Called in response to the new note form being submitted
+
+ Adds new note to list.
+ ###
+ addNote: (xhr, note, status) =>
+ @renderNote(note)
+ @updateVotes()
+
+ ###
+ Called in response to the new note form being submitted
+
+ Adds new note to list.
+ ###
+ addDiscussionNote: (xhr, note, status) =>
+ @renderDiscussionNote(note)
+
+ ###
+ Called in response to the edit note form being submitted
+
+ Updates the current note field.
+ ###
+ updateNote: (xhr, note, status) =>
+ note_li = $("#note_" + note.id)
+ note_li.replaceWith(note.html)
+ code = "#note_" + note.id + " .highlight pre code"
+ $(code).each (i, e) ->
+ hljs.highlightBlock(e)
+
+ ###
+ Called in response to clicking the edit note link
+
+ Replaces the note text with the note edit form
+ Adds a hidden div with the original content of the note to fill the edit note form with
+ if the user cancels
+ ###
+ showEditForm: (e) ->
+ e.preventDefault()
+ note = $(this).closest(".note")
+ note.find(".note-text").hide()
+
+ # Show the attachment delete link
+ note.find(".js-note-attachment-delete").show()
+ GitLab.GfmAutoComplete.setup()
+ form = note.find(".note-edit-form")
+ form.show()
+ form.find("textarea").focus()
+
+ ###
+ Called in response to clicking the edit note link
+
+ Hides edit form
+ ###
+ cancelEdit: (e) ->
+ e.preventDefault()
+ note = $(this).closest(".note")
+ note.find(".note-text").show()
+ note.find(".js-note-attachment-delete").hide()
+ note.find(".note-edit-form").hide()
+
+ ###
+ Called in response to deleting a note of any kind.
+
+ Removes the actual note from view.
+ Removes the whole discussion if the last note is being removed.
+ ###
+ removeNote: ->
+ note = $(this).closest(".note")
+ notes = note.closest(".notes")
+
+ # check if this is the last note for this line
+ if notes.find(".note").length is 1
+
+ # for discussions
+ notes.closest(".discussion").remove()
+
+ # for diff lines
+ notes.closest("tr").remove()
+
+ note.remove()
+
+ ###
+ Called in response to clicking the delete attachment link
+
+ Removes the attachment wrapper view, including image tag if it exists
+ Resets the note editing form
+ ###
+ removeAttachment: ->
+ note = $(this).closest(".note")
+ note.find(".note-attachment").remove()
+ note.find(".note-text").show()
+ note.find(".js-note-attachment-delete").hide()
+ note.find(".note-edit-form").hide()
+
+ ###
+ Called when clicking on the "reply" button for a diff line.
+
+ Shows the note form below the notes.
+ ###
+ replyToDiscussionNote: (e) =>
+ form = $(".js-new-note-form")
+ replyLink = $(e.target)
+ replyLink.hide()
+
+ # insert the form after the button
+ form.clone().insertAfter replyLink
+
+ # show the form
+ @setupDiscussionNoteForm(replyLink, replyLink.next("form"))
+
+ ###
+ Shows the diff or discussion form and does some setup on it.
+
+ Sets some hidden fields in the form.
+
+ Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
+ and "noteableId" data attributes set.
+ ###
+ setupDiscussionNoteForm: (dataHolder, form) =>
+ # setup note target
+ form.attr "rel", dataHolder.data("discussionId")
+ form.find("#note_commit_id").val dataHolder.data("commitId")
+ form.find("#note_line_code").val dataHolder.data("lineCode")
+ form.find("#note_noteable_type").val dataHolder.data("noteableType")
+ form.find("#note_noteable_id").val dataHolder.data("noteableId")
+ @setupNoteForm form
+ form.find(".js-note-text").focus()
+ form.addClass "js-discussion-note-form"
+
+ ###
+ General note form setup.
+
+ deactivates the submit button when text is empty
+ hides the preview button when text is empty
+ setup GFM auto complete
+ show the form
+ ###
+ setupNoteForm: (form) =>
+ disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
+ form.removeClass "js-new-note-form"
+ form.removeClass "js-new-note-form"
+ GitLab.GfmAutoComplete.setup()
+
+ # setup preview buttons
+ previewButton = form.find(".js-note-preview-button")
+ form.find(".js-note-text").on "input", ->
+ if $(this).val().trim() isnt ""
+ previewButton.removeClass("turn-off").addClass "turn-on"
+ else
+ previewButton.removeClass("turn-on").addClass "turn-off"
+
+ form.show()
+
+ ###
+ Called when clicking on the "add a comment" button on the side of a diff line.
+
+ Inserts a temporary row for the form below the line.
+ Sets up the form and shows it.
+ ###
+ addDiffNote: (e) =>
+ e.preventDefault()
+ link = e.target
+ form = $(".js-new-note-form")
+ row = $(link).closest("tr")
+ nextRow = row.next()
+
+ # does it already have notes?
+ if nextRow.is(".notes_holder")
+ replyButton = nextRow.find(".js-discussion-reply-button")
+ if replyButton.length > 0
+ $.proxy(@replyToDiscussionNote, replyButton).call()
+ else
+ # add a notes row and insert the form
+ row.after " | |
"
+ form.clone().appendTo row.next().find(".notes_content")
+
+ # show the form
+ @setupDiscussionNoteForm $(link), row.next().find("form")
+
+ ###
+ Called in response to "cancel" on a diff note form.
+
+ Shows the reply button again.
+ Removes the form and if necessary it's temporary row.
+ ###
+ removeDiscussionNoteForm: (form)->
+ row = form.closest("tr")
+
+ # show the reply button (will only work for replies)
+ form.prev(".js-discussion-reply-button").show()
+ if row.is(".js-temp-notes-holder")
+ # remove temporary row for diff lines
+ row.remove()
+ else
+ # only remove the form
+ form.remove()
+
+
+ cancelDiscussionForm: (e) =>
+ e.preventDefault()
+ form = $(".js-new-note-form")
+ form = $(e.target).closest(".js-discussion-note-form")
+ @removeDiscussionNoteForm(form)
+
+ updateVotes: ->
+ (new NotesVotes).updateVotes()
+
+ ###
+ Called after an attachment file has been selected.
+
+ Updates the file name for the selected attachment.
+ ###
+ updateFormAttachment: ->
+ form = $(this).closest("form")
+
+ # get only the basename
+ filename = $(this).val().replace(/^.*[\\\/]/, "")
+ form.find(".js-attachment-filename").text filename
+
+ ###
+ Called when the tab visibility changes
+ ###
+ visibilityChange: =>
+ @refresh()
+
+@Notes = Notes
diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee
new file mode 100644
index 0000000000..b31eb9ac9d
--- /dev/null
+++ b/app/assets/javascripts/notes_votes.js.coffee
@@ -0,0 +1,22 @@
+class NotesVotes
+ updateVotes: ->
+ votes = $("#votes .votes")
+ notes = $("#notes-list .note .vote")
+
+ # only update if there is a vote display
+ if votes.size()
+ upvotes = notes.filter(".upvote").size()
+ downvotes = notes.filter(".downvote").size()
+ votesCount = upvotes + downvotes
+ upvotesPercent = (if votesCount then (100.0 / votesCount * upvotes) else 0)
+ downvotesPercent = (if votesCount then (100.0 - upvotesPercent) else 0)
+
+ # change vote bar lengths
+ votes.find(".bar-success").css "width", upvotesPercent + "%"
+ votes.find(".bar-danger").css "width", downvotesPercent + "%"
+
+ # replace vote numbers
+ votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
+ votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
+
+@NotesVotes = NotesVotes
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index 5bd11d273a..1f763e8b95 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -19,8 +19,9 @@
data: "limit=" + @limit + "&offset=" + @offset
complete: ->
$(".loading").hide()
-
- dataType: "script"
+ success: (data) ->
+ Pager.append(data.count, data.html)
+ dataType: "json"
append: (count, html) ->
$(".content_list").append html
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 744f3086d5..b37a78ac19 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -26,3 +26,5 @@ $ ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
+
+ $('.profile-groups-avatars').tooltip("placement": "top")
\ No newline at end of file
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index bdb18574b6..4262418fd5 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -35,8 +35,14 @@ $ ->
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
- $(".clone").text("").append 'git remote add origin ' + $(@).data 'clone'
+ $(".clone").text("").append $(@).data 'clone'
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
+
+ $('.hide-no-ssh-message').on 'click', (e) ->
+ path = '/'
+ $.cookie('hide_no_ssh_message', 'false', { path: path })
+ $(@).parents('.no-ssh-key-message').hide()
+ e.preventDefault()
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
new file mode 100644
index 0000000000..b0e39610fe
--- /dev/null
+++ b/app/assets/javascripts/project_users_select.js.coffee
@@ -0,0 +1,63 @@
+@projectUsersSelect =
+ init: ->
+ $('.ajax-project-users-select').each (i, select) ->
+ project_id = $(select).data('project-id') || $('body').data('project-id')
+
+ $(select).select2
+ placeholder: $(select).data('placeholder') || "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.projectUsers project_id, query.term, (users) ->
+ data = { results: users }
+
+ nullUser = {
+ name: 'Unassigned',
+ avatar: null,
+ username: 'none',
+ id: ''
+ }
+
+ data.results.unshift(nullUser)
+
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
+
+
+ formatResult: projectUsersSelect.projectUserFormatResult
+ formatSelection: projectUsersSelect.projectUserFormatSelection
+ dropdownCssClass: "ajax-project-users-dropdown"
+ dropdownAutoWidth: true
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
+ projectUserFormatResult: (user) ->
+ if user.avatar_url
+ avatar = user.avatar_url
+ else if gon.gravatar_enabled
+ avatar = gon.gravatar_url
+ avatar = avatar.replace('%{hash}', md5(user.email))
+ avatar = avatar.replace('%{size}', '24')
+ else
+ avatar = gon.relative_url_root + "/assets/no_avatar.png"
+
+ if user.id == ''
+ avatarMarkup = ''
+ else
+ avatarMarkup = ""
+
+ "
+ #{avatarMarkup}
+
#{user.name}
+
#{user.username}
+
"
+
+ projectUserFormatSelection: (user) ->
+ user.name
+
+$ ->
+ projectUsersSelect.init()
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index 3418690e10..e144dfa1d6 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,7 +1,12 @@
class SearchAutocomplete
- constructor: (json) ->
+ constructor: (search_autocomplete_path, project_id, project_ref) ->
+ project_id = '' unless project_id
+ project_ref = '' unless project_ref
+ query = "?project_id=" + project_id + "&project_ref=" + project_ref
+
$("#search").autocomplete
- source: json
+ source: search_autocomplete_path + query
+ minLength: 1
select: (event, ui) ->
location.href = ui.item.url
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 4844364416..834c7e5dab 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -46,11 +46,7 @@ class window.ContributorsGraph
class window.ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
- if $(window).width() > 1214
- @width = 1100
- else
- @width = 870
-
+ @width = $('.container').width() - 70
@height = 200
@x = null
@y = null
@@ -88,7 +84,6 @@ class window.ContributorsMasterGraph extends ContributorsGraph
x(d.date)
).y0(@height).y1((d) ->
xa = d.commits = d.commits ? d.additions ? d.deletions
- console.log(xa)
y(xa)
).interpolate("basis")
create_brush: ->
@@ -124,11 +119,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
class window.ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
- if $(window).width() > 1214
- @width = 490
- else
- @width = 380
-
+ @width = $('.container').width()/2 - 100
@height = 200
@x = null
@y = null
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 327667fede..ce9a505b1e 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,17 +1,19 @@
$ ->
userFormatResult = (user) ->
- if user.avatar
- avatar = user.avatar.url
- else
+ if user.avatar_url
+ avatar = user.avatar_url
+ else if gon.gravatar_enabled
avatar = gon.gravatar_url
avatar = avatar.replace('%{hash}', md5(user.email))
avatar = avatar.replace('%{size}', '24')
- markup = ""
- markup += "
"
- markup += "
" + user.name + "
"
- markup += "
" + user.username + "
"
- markup += "
"
- markup
+ else
+ avatar = gon.relative_url_root + "/assets/no_avatar.png"
+
+ "
+
+
#{user.name}
+
#{user.username}
+
"
userFormatSelection = (user) ->
user.name
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index b1a23427ad..c53873f95a 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,59 +2,50 @@
* 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.ui.datepicker
+ *= require jquery.ui.autocomplete
*= require jquery.atwho
- *= require chosen
*= require select2
+ *= require highlightjs.min
*= require_self
+ *= require nprogress
+ *= require nprogress-bootstrap
*/
+@import "main/*";
+
/**
- * GitLab bootstrap:
+ * Customized Twitter bootstrap
*/
-@import "gitlab_bootstrap.scss";
+@import 'gl_bootstrap';
-@import "common.scss";
-@import "selects.scss";
+/**
+ * Font icons
+ *
+ */
+@import "font-awesome";
-@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 "sections/admin.scss";
-@import "sections/wiki.scss";
-@import "sections/wall.scss";
-@import "sections/dashboard.scss";
-@import "sections/stat_graph.scss";
+/**
+ * Generic css (forms, nav etc):
+ */
+@import "generic/*";
-@import "highlight/white.scss";
-@import "highlight/dark.scss";
-@import "highlight/solarized_dark.scss";
-@import "highlight/monokai.scss";
+/**
+ * Page specific styles (issues, projects etc):
+ */
+@import "sections/*";
+
+/**
+ * Code highlight
+ */
+@import "highlight/*";
/**
* 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";
+@import "themes/*";
/**
* Styles for JS behaviors.
*/
@import "behaviors.scss";
-
diff --git a/app/assets/stylesheets/gitlab_bootstrap/avatar.scss b/app/assets/stylesheets/generic/avatar.scss
similarity index 100%
rename from app/assets/stylesheets/gitlab_bootstrap/avatar.scss
rename to app/assets/stylesheets/generic/avatar.scss
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss
new file mode 100644
index 0000000000..3536a68f41
--- /dev/null
+++ b/app/assets/stylesheets/generic/blocks.scss
@@ -0,0 +1,19 @@
+.light-well {
+ background: #f9f9f9;
+ padding: 15px;
+}
+
+.centered-light-block {
+ text-align: center;
+ color: #888;
+ margin: 20px;
+}
+
+.nothing-here-block {
+ text-align: center;
+ padding: 20px;
+ color: #666;
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 36px;
+}
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
new file mode 100644
index 0000000000..dd0a2938cf
--- /dev/null
+++ b/app/assets/stylesheets/generic/buttons.scss
@@ -0,0 +1,191 @@
+.btn {
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: normal;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 6px 12px;
+ font-size: 13px;
+ line-height: 18px;
+ border-radius: 4px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+ color: #444444;
+ background-color: #fff;
+ border-color: #ccc;
+ text-shadow: none;
+
+ &.hover,
+ &:hover {
+ color: #444444;
+ text-decoration: none;
+ background-color: #ebebeb;
+ border-color: #adadad;
+ }
+
+ &.focus,
+ &:focus {
+ color: #444444;
+ text-decoration: none;
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+ }
+
+ &.active,
+ &:active {
+ outline: 0;
+ background-image: none;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ }
+
+ &.disabled,
+ &[disabled] {
+ cursor: not-allowed;
+ pointer-events: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+
+ &.btn-primary {
+ color: #ffffff;
+ background-color: #429bca;
+ border-color: #358ebd;
+
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #ffffff;
+ background-color: #3286b1;
+ border-color: #286e8e;
+ }
+ }
+
+ &.btn-success {
+ color: #ffffff;
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+
+
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #ffffff;
+ background-color: #47a447;
+ border-color: #398439;
+ }
+ }
+
+ &.btn-danger {
+ color: #ffffff;
+ background-color: #d9534f;
+ border-color: #d43f3a;
+
+
+ &.hover,
+ &:hover,
+ &.disabled,
+ &[disabled] {
+ color: #ffffff;
+ background-color: #d2322d;
+ border-color: #ac2925;
+ }
+ }
+
+ &.btn-new {
+ @extend .btn-success;
+ }
+
+ &.btn-create {
+ @extend .wide;
+ @extend .btn-success;
+ }
+
+ &.btn-save {
+ @extend .wide;
+ @extend .btn-primary;
+ }
+
+ &.btn-remove {
+ @extend .btn-danger;
+ }
+
+ &.btn-cancel {
+ float: right;
+ }
+
+ &.wide {
+ padding-left: 20px;
+ padding-right: 20px;
+ }
+
+ &.btn-small {
+ padding: 2px 10px;
+ font-size: 12px;
+ }
+
+ &.btn-tiny {
+ font-size: 11px;
+ padding: 2px 6px;
+ line-height: 16px;
+ margin: 2px;
+ }
+
+ &.btn-close {
+ color: #B94A48;
+ font-weight: bold;
+ &:hover {
+ color: #B94A48;
+ }
+ }
+
+ &.btn-reopen {
+ color: #468847;
+ font-weight: bold;
+ &:hover {
+ color: #468847;
+ }
+ }
+
+ &.btn-grouped {
+ margin-right: 7px;
+ float: left;
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
+}
+
+.btn-block {
+ width: 100%;
+ margin: 0;
+ margin-bottom: 15px;
+ &.btn {
+ padding: 6px 0;
+ }
+}
+
+.btn-group {
+ &.btn-grouped {
+ margin-right: 7px;
+ float: left;
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
+}
+
+.btn-group-small > .btn { @extend .btn.btn-small; }
+.btn-group-tiny > .btn { @extend .btn.btn-tiny; }
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/generic/common.scss
similarity index 60%
rename from app/assets/stylesheets/common.scss
rename to app/assets/stylesheets/generic/common.scss
index dbd50b4dcc..4e660d0b1e 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -1,22 +1,85 @@
-html {
- overflow-y: scroll;
+/** COLORS **/
+.cgray { color: gray }
+.clgray { color: #BBB }
+.cred { color: #D12F19 }
+.cgreen { color: #4a2 }
+.cblue { color: #29A }
+.cblack { color: #111 }
+.cdark { color: #444 }
+.camber { color: #ffc000 }
+.cwhite { color: #fff!important }
+.bgred { background: #F2DEDE!important }
+
+/** COMMON CLASSES **/
+.prepend-top-10 { margin-top:10px }
+.prepend-top-20 { margin-top:20px }
+.prepend-left-10 { margin-left:10px }
+.prepend-left-20 { margin-left:20px }
+.append-right-10 { margin-right:10px }
+.append-right-20 { margin-right:20px }
+.append-bottom-10 { margin-bottom:10px }
+.append-bottom-15 { margin-bottom:15px }
+.append-bottom-20 { margin-bottom:20px }
+.inline { display: inline-block }
+
+.underlined-link { text-decoration: underline; }
+.hint { font-style: italic; color: #999; }
+.light { color: #888 }
+
+.slead {
+ color: #666;
+ font-size: 14px;
+ margin-bottom: 12px;
+ font-weight: normal;
+ line-height: 24px;
}
-/** LAYOUT **/
-
-body {
- margin-bottom: 20px;
+.tab-content {
+ overflow: visible;
}
-.container {
- padding-top: 0;
- z-index: 5;
+pre {
+ &.clean {
+ background: none;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ &.well-pre {
+ border: 1px solid #EEE;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #555;
+ }
}
-.container .content {
- margin: 0 0;
+.dropdown-menu > li > a {
+ text-shadow: none;
}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ background: #29b;
+ color: #FFF
+}
+
+.breadcrumb > li + li:before {
+ content: "/";
+ padding: 0;
+ color: #666;
+}
+
+.str-truncated {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ white-space: nowrap;
+ max-width: 82%;
+}
+
+/** FLASH message **/
.author_link {
color: $link_color;
}
@@ -35,39 +98,10 @@ table a code {
.loading {
margin: 20px auto;
- background: url(ajax_loader.gif) no-repeat center center;
- width: 40px;
height: 40px;
- &.loading-gray {
- background: url(ajax_loader_gray.gif) no-repeat center center;
- }
-}
-
-/** FLASH message **/
-.flash-container {
- display: none;
- cursor: pointer;
- margin: 0;
+ color: #555;
+ font-size: 32px;
text-align: center;
- color: #fff;
- font-size: 14px;
- position: fixed;
- bottom: 0;
- width: 100%;
- opacity: 0.8;
- z-index: 100;
-
- .flash-notice {
- background: #49C;
- padding: 10px;
- text-shadow: 0 1px 1px #178;
- }
-
- .flash-alert {
- background: #C67;
- text-shadow: 0 1px 1px #945;
- padding: 10px;
- }
}
span.update-author {
@@ -90,19 +124,6 @@ span.update-author {
display: inline;
}
-ul.breadcrumb {
- background: white;
- border: none;
- li {
- display: inline;
- text-shadow: 0 1px 0 white
- }
-
- a {
- font-size: 16px;
- }
-}
-
.line_holder {
&:hover {
td {
@@ -117,18 +138,6 @@ p.time {
margin: 30px 3px 3px 2px;
}
-.search-holder {
- label, input {
- height: 30px;
- padding: 0;
- font-size: 14px;
- }
- label {
- line-height: 30px;
- color: #666;
- }
-}
-
.highlight {
text-shadow: none;
}
@@ -190,24 +199,8 @@ li.note {
cursor: pointer;
}
-.merge-request,
-.issue {
- &.today{
- background: #EFE;
- border-color: #CEC;
- }
- &.closed {
- background: #F5f5f5;
- border-color: #E5E5E5;
- }
- &.merged {
- background: #F5f5f5;
- border-color: #E5E5E5;
- }
-}
-
.git_error_tips {
- @extend .span6;
+ @extend .col-md-6;
text-align: left;
margin-top: 40px;
pre {
@@ -220,7 +213,6 @@ li.note {
.error-message {
padding: 10px;
background: #C67;
- padding-left: 20px;
margin: 0;
color: #FFF;
@@ -228,8 +220,25 @@ li.note {
color: #fff;
text-decoration: underline;
}
- &.centered {
+}
+
+.no-ssh-key-message {
+ padding: 10px 0;
+ background: #C67;
+ margin: 0;
+ color: #FFF;
+ margin-top: -1px;
+ text-align: center;
+
+ a {
+ color: #fff;
+ text-decoration: underline;
+ }
+
+ .links-xs {
text-align: center;
+ font-size: 16px;
+ padding: 5px;
}
}
@@ -253,15 +262,6 @@ li.note {
}
}
-pre {
- &.clean {
- background: none;
- border: none;
- margin: 0;
- padding: 0;
- }
-}
-
.milestone {
&.milestone-closed {
background: #eee;
@@ -298,10 +298,6 @@ img.emoji {
width: 20px;
}
-.appear-data {
- display: none;
-}
-
.chart {
overflow: hidden;
height: 220px;
@@ -317,11 +313,6 @@ img.emoji {
margin-bottom: 10px;
}
-.group-name {
- font-size: 14px;
- line-height: 24px;
-}
-
table {
td.permission-x {
background: #D9EDF7 !important;
@@ -338,7 +329,33 @@ table {
min-height: 100px;
}
-.navbar-gitlab .navbar-inner .nav > li .btn-sign-in {
- @extend .btn-new;
- padding: 5px 15px;
+.broadcast-message {
+ padding: 10px;
+ text-align: center;
+ background: #555;
+ color: #BBB;
+}
+
+.broadcast-message-preview {
+ @extend .broadcast-message;
+ margin-bottom: 20px;
+}
+
+.btn-sign-in {
+ margin-top: 7px;
+ text-shadow: none;
+}
+
+.side-filters {
+ fieldset {
+ margin-bottom: 15px;
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .container .content { margin-top: 20px; }
+}
+
+.wiki .highlight, .note-body .highlight {
+ margin-bottom: 9px;
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/generic/files.scss
similarity index 56%
rename from app/assets/stylesheets/gitlab_bootstrap/files.scss
rename to app/assets/stylesheets/generic/files.scss
index a286e530cd..9e4207965f 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/files.scss
+++ b/app/assets/stylesheets/generic/files.scss
@@ -11,29 +11,32 @@
}
.file-title {
- border-bottom: 1px solid #bbb;
- @include bg-dark-gray-gradient;
+ background: #EEE;
+ border-bottom: 1px solid #CCC;
text-shadow: 0 1px 1px #fff;
margin: 0;
- font-weight: normal;
- font-weight: bold;
text-align: left;
- color: $style_color;
padding: 9px 10px;
- height: 18px;
.options {
float: right;
margin-top: -5px;
}
+ .left-options {
+ margin-top: -3px;
+ }
+
.file_name {
- color: $style_color;
+ font-weight: bold;
+ padding-left: 3px;
font-size: 14px;
- text-shadow: 0 1px 1px #fff;
+
small {
- color: #999;
+ color: #888;
font-size: 13px;
+ font-weight: normal;
+ padding-left: 10px;
}
}
}
@@ -46,14 +49,14 @@
text-align: center;
img {
padding: 100px;
- max-width: 300px;
+ max-width: 50%;
}
}
&.wiki {
- padding: 20px;
font-size: 14px;
line-height: 1.6;
+ padding: 25px;
.highlight {
margin-bottom: 9px;
@@ -144,75 +147,6 @@
*/
&.code {
padding: 0;
-
- table.lines {
- border: none;
- box-shadow: none;
- margin: 0px;
- padding: 0px;
- table-layout: fixed;
-
- pre {
- border: none;
- border-radius: 0;
- font-family: $monospace_font;
- font-size: 12px !important;
- line-height: 16px !important;
- margin: 0;
- padding: 10px 0;
- }
- td {
- border: none;
- margin: 0;
- padding: 0;
- vertical-align: top;
-
- &:first-child {
- background: #eee;
- width: 50px;
- }
- &:last-child {
- }
- }
- tr:hover {
- background: none;
- }
-
- pre.line_numbers {
- color: #666;
- padding: 10px 6px 10px 0;
- text-align: right;
- background: #EEE;
-
- a {
- color: #666;
-
- i {
- display: none;
- font-size: 14px;
- line-height: 14px;
- }
- &:hover i {
- display: inherit;
- }
- }
- }
-
- .highlight {
- border-left: 1px solid #DEE2E3;
- overflow: auto;
- overflow-y: hidden;
-
- pre {
- white-space: pre;
- word-wrap: normal;
-
- .line {
- padding: 0 10px;
- }
- }
- }
- }
}
}
}
diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/generic/flash.scss
new file mode 100644
index 0000000000..95d28aaef6
--- /dev/null
+++ b/app/assets/stylesheets/generic/flash.scss
@@ -0,0 +1,25 @@
+.flash-container {
+ display: none;
+ cursor: pointer;
+ margin: 0;
+ text-align: center;
+ color: #fff;
+ font-size: 14px;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ opacity: 0.8;
+ z-index: 100;
+
+ .flash-notice {
+ background: #49C;
+ padding: 10px;
+ text-shadow: 0 1px 1px #178;
+ }
+
+ .flash-alert {
+ background: #C67;
+ text-shadow: 0 1px 1px #945;
+ padding: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
new file mode 100644
index 0000000000..36551f85b6
--- /dev/null
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -0,0 +1,100 @@
+input[type='search'].search-text-input {
+ background-image: image-url("icon-search.png");
+ background-repeat: no-repeat;
+ background-position: 10px;
+ padding-left: 25px;
+}
+
+input[type='text'].danger {
+ background: #F2DEDE!important;
+ border-color: #D66;
+ text-shadow: 0 1px 1px #fff
+}
+
+fieldset legend {
+ font-size: 16px;
+}
+
+.datetime-controls {
+ select {
+ width: 100px;
+ }
+}
+
+.form-actions {
+ padding: 17px 20px 18px;
+ margin-top: 18px;
+ margin-bottom: 18px;
+ background-color: whitesmoke;
+ border-top: 1px solid #e5e5e5;
+ padding-left: 17%;
+}
+
+label {
+ &.control-label {
+ @extend .col-sm-2;
+ }
+
+ &.inline-label {
+ margin: 0;
+ }
+}
+
+.inline-input-group {
+ width: 250px;
+}
+
+.input-mx-250 {
+ max-width: 250px;
+}
+
+.input-mn-300 {
+ min-width: 300px;
+}
+
+.custom-form-control {
+ width: 150px;
+}
+
+@media (min-width: $screen-sm-min) {
+ .custom-form-control {
+ width: 150px;
+ }
+}
+
+/* Medium devices (desktops, 992px and up) */
+@media (min-width: $screen-md-min) {
+ .custom-form-control {
+ width: 170px;
+ }
+}
+
+/* Large devices (large desktops, 1200px and up) */
+@media (min-width: $screen-lg-min) {
+ .custom-form-control {
+ width: 200px;
+ }
+}
+
+.commit-message-container {
+ background-color: $body-bg;
+ position: relative;
+ font-family: $monospace_font;
+ $left: 12px;
+ .max-width-marker {
+ color: rgba(0, 0, 0, 0.0);
+ font-family: inherit;
+ left: $left;
+ height: 100%;
+ border-right: 1px solid mix($input-border, white);
+ position: absolute;
+ z-index: 1;
+ }
+ > textarea {
+ background-color: rgba(0, 0, 0, 0.0);
+ font-family: inherit;
+ padding-left: $left;
+ position: relative;
+ z-index: 2;
+ }
+}
diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss
new file mode 100644
index 0000000000..4110bddf4f
--- /dev/null
+++ b/app/assets/stylesheets/generic/highlight.scss
@@ -0,0 +1,64 @@
+.highlighted-data {
+ border: none;
+ box-shadow: none;
+ margin: 0px;
+ padding: 0px;
+ table-layout: fixed;
+
+ pre {
+ padding: 10px;
+ border: none;
+ border-radius: 0;
+ font-family: $monospace_font;
+ font-size: 12px !important;
+ line-height: 16px !important;
+ margin: 0;
+
+ code {
+ white-space: pre;
+ word-wrap: normal;
+ padding: 0;
+
+ .line {
+ display: inline;
+ }
+ }
+ }
+
+ .hljs {
+ padding: 0;
+ }
+
+ .line-numbers {
+ padding: 10px;
+ text-align: right;
+ float: left;
+
+ a {
+ font-family: $monospace_font;
+ display: block;
+ font-size: 12px !important;
+ line-height: 16px !important;
+ white-space: nowrap;
+
+ i {
+ visibility: hidden;
+ @extend .pull-left;
+ }
+
+ &:hover i {
+ visibility: visible;
+ }
+ }
+ }
+
+ .highlight {
+ overflow: auto;
+ overflow-y: hidden;
+
+ pre {
+ white-space: pre;
+ word-wrap: normal;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
new file mode 100644
index 0000000000..d4d3361bc7
--- /dev/null
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -0,0 +1,128 @@
+/**
+ * Issue box:
+ * Huge block (one per page) for storing title, descripion and other information.
+ * Used for Issue#show page, MergeRequest#show page etc
+ *
+ * CLasses:
+ * .issue-box - Regular box
+ */
+
+.issue-box {
+ color: #666;
+ margin:20px 0;
+ background: #FFF;
+ border: 1px solid #EEE;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
+
+ &.issue-box-closed {
+ border-color: #DA4E49;
+ .state {
+ background-color: #f2dede;
+ border-color: #ebccd1;
+ color: #a94442;
+ .state-label {
+ background: #DA4E49;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-merged {
+ border-color: #31708f;
+ .state {
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+ color: #31708f;
+ .state-label {
+ background: #31708f;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-open {
+ border-color: #4A4;
+ .state {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ color: #3c763d;
+ .state-label {
+ background: #4A4;
+ color: #FFF;
+ }
+ }
+ }
+
+ &.issue-box-expired {
+ border-color: #cea61b;
+ .state {
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+ color: #8a6d3b;
+ .state-label {
+ background: #cea61b;
+ color: #FFF;
+ }
+ }
+ }
+
+ .control-group {
+ margin-bottom: 0;
+ }
+
+ .state {
+ border-bottom: 1px solid #DDD;
+ line-height: 32px;
+ }
+
+ .title {
+ font-size: 22px;
+ font-weight: 500;
+ line-height: 1.5;
+ margin: 0;
+ color: #333;
+ padding-bottom: 0;
+ padding: 15px 25px;
+ }
+
+ .context {
+ border: none;
+ border-top: 1px solid #eee;
+ padding: 15px 25px;
+
+ // Reset text align for children
+ .text-right > * { text-align: left; }
+
+ @media (max-width: $screen-xs-max) {
+ // Don't right align on mobile
+ .text-right { text-align: left; }
+
+ .row .col-md-6 {
+ padding-top: 5px;
+ }
+ }
+ }
+
+ .description {
+ padding: 0 25px 15px 25px;
+ }
+
+ .title, .context, .description {
+ .clearfix {
+ margin: 0;
+ }
+ }
+
+ .state-label {
+ font-size: 14px;
+ padding: 1px 25px;
+ text-align: center;
+ text-shadow: none;
+ display: inline-block;
+ line-height: 34px;
+ }
+
+ .creator {
+ padding: 2px 15px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/generic/jquery.scss
new file mode 100644
index 0000000000..6b29accb31
--- /dev/null
+++ b/app/assets/stylesheets/generic/jquery.scss
@@ -0,0 +1,55 @@
+.ui-widget {
+ font-family: $regular_font;
+ font-size: $font-size-base;
+
+ &.ui-datepicker-inline {
+ border: 1px solid #DDD;
+ padding: 10px;
+ width: 270px;
+
+ .ui-datepicker-header {
+ background: #FFF;
+ border-color: #DDD;
+ }
+
+ .ui-datepicker-calendar td a {
+ padding: 5px;
+ text-align: center;
+ }
+ }
+
+ &.ui-autocomplete {
+ border-color: #DDD;
+ padding: 0;
+ margin-top: 2px;
+ z-index: 1001;
+
+ .ui-menu-item a {
+ padding: 4px 10px;
+ }
+ }
+
+ .ui-state-default {
+ border: 1px solid #FFF;
+ background: #FFF;
+ color: #777;
+ }
+
+ .ui-state-highlight {
+ border: 1px solid #EEE;
+ background: #EEE;
+ }
+
+ .ui-state-active {
+ border: 1px solid $bg_style_color;
+ background: $bg_style_color;
+ color: #FFF;
+ }
+
+ .ui-state-hover,
+ .ui-state-focus {
+ border: 1px solid $hover;
+ background: $hover;
+ color: #333;
+ }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/generic/lists.scss
similarity index 82%
rename from app/assets/stylesheets/gitlab_bootstrap/lists.scss
rename to app/assets/stylesheets/generic/lists.scss
index 83066b5bee..963768044d 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -4,13 +4,21 @@
*/
.well-list {
margin: 0;
+ padding: 0;
list-style: none;
+
li {
padding: 10px;
min-height: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ &:after {
+ content: " ";
+ display: table;
+ clear: both;
+ }
+
&.disabled {
color: #888;
}
@@ -21,6 +29,12 @@
}
}
+ &.warning-row {
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+ color: #8a6d3b;
+ }
+
&.smoke { background-color: #f5f5f5; }
&:hover {
@@ -38,6 +52,12 @@
.author { color: #999; }
+ .list-item-name {
+ float: left;
+ position: relative;
+ top: 3px;
+ }
+
p {
padding-top: 1px;
margin: 0;
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
new file mode 100644
index 0000000000..03597c1fc7
--- /dev/null
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -0,0 +1,123 @@
+/** Select2 selectbox style override **/
+.select2-container, .select2-container.select2-drop-above {
+ .select2-choice {
+ background: #FFF;
+ border-color: #BBB;
+
+ .select2-arrow {
+ background: #FFF;
+ }
+ }
+}
+
+.select2-drop-active {
+ border: 1px solid #BBB !important;
+ margin-top: 4px;
+
+ &.select2-drop-above {
+ margin-bottom: 8px;
+ }
+
+ .select2-search input {
+ background: #fafafa;
+ border-color: #DDD;
+ }
+
+ .select2-results {
+ max-height: 350px;
+ .select2-highlighted {
+ background: $bg_style_color;
+ }
+ }
+}
+
+select {
+ &.select2 {
+ width: 100px;
+ }
+
+ &.select2-sm {
+ width: 100px;
+ }
+}
+
+@media (min-width: $screen-sm-min) {
+ select {
+ &.select2 {
+ width: 150px;
+ }
+ &.select2-sm {
+ width: 120px;
+ }
+ }
+}
+
+/* Medium devices (desktops, 992px and up) */
+@media (min-width: $screen-md-min) {
+ select {
+ &.select2 {
+ width: 170px;
+ }
+ &.select2-sm {
+ width: 140px;
+ }
+ }
+}
+
+/* Large devices (large desktops, 1200px and up) */
+@media (min-width: $screen-lg-min) {
+ select {
+ &.select2 {
+ width: 200px;
+ }
+ &.select2-sm {
+ width: 150px;
+ }
+ }
+}
+
+
+/** Branch/tag selector **/
+.project-refs-form .select2-container {
+ margin-right: 10px;
+}
+
+.ajax-users-dropdown, .ajax-project-users-dropdown {
+ .select2-search {
+ padding-top: 4px;
+ }
+}
+
+.ajax-users-select {
+ width: 400px;
+
+ &.input-large {
+ width: 210px;
+ }
+
+ &.input-clamp {
+ max-width: 100%;
+ }
+}
+
+.user-result {
+ .user-image {
+ float: left;
+ }
+ .user-name {
+ }
+ .user-username {
+ color: #999;
+ }
+}
+
+.namespace-result {
+ .namespace-kind {
+ color: #AAA;
+ font-weight: normal;
+ }
+ .namespace-path {
+ margin-left: 10px;
+ font-weight: bolder;
+ }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/generic/typography.scss
similarity index 64%
rename from app/assets/stylesheets/gitlab_bootstrap/typography.scss
rename to app/assets/stylesheets/generic/typography.scss
index d398655637..8cc72d7f07 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -2,11 +2,6 @@
* Headers
*
*/
-h1, h2, h3, h4, h5, h6 {
- font-weight: 500;
- line-height: 1.1;
-}
-
h1.page-title {
@include page-title;
font-size: 28px;
@@ -52,7 +47,7 @@ a {
text-decoration: underline;
}
- &.dark {
+ &.darken {
color: $style_color;
}
@@ -93,12 +88,28 @@ a:focus {
.wiki {
@include md-typography;
- font-size: 14px;
- line-height: 1.6;
- .white .highlight pre {
- background: #f5f5f5;
+ /* Link to current header. */
+ h1, h2, h3, h4, h5, h6 {
+ position: relative;
+ &:hover > :last-child {
+ $size: 16px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ margin-top: -$size/2;
+ margin-right: 0px;
+ padding-right: 20px;
+ display: inline-block;
+ width: $size;
+ height: $size;
+ background-image: image-url("icon-link.png");
+ background-size: contain;
+ background-repeat: no-repeat;
+ }
}
+
ul {
+ padding: 0;
margin: 0 0 9px 25px !important;
}
}
@@ -106,3 +117,11 @@ a:focus {
.md {
@include md-typography;
}
+
+/**
+ * Textareas intended for GFM
+ *
+ */
+textarea.js-gfm-input {
+ font-family: $monospace_font;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/generic/ui_box.scss
similarity index 54%
rename from app/assets/stylesheets/gitlab_bootstrap/blocks.scss
rename to app/assets/stylesheets/generic/ui_box.scss
index 53de8067ab..7a977eae70 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
+++ b/app/assets/stylesheets/generic/ui_box.scss
@@ -1,106 +1,55 @@
/**
- * ===================================
- * Contain UI block elements:
+ * UI box:
+ * Block element for separating information on page.
+ * Used for storing issues lists, grouped data.
+ * You can have multiple ui boxes on one page
+ *
+ * Classes:
* .ui-box - for any block & widgets
- * ===================================
- */
-
-/**
- * UI Block
+ * .ui-box.ui-box-small - same but with smaller title
+ * .ui-box.ui-box-danger - with red title
+ *
+ * Ex. 1: List
+ * .ui-box
+ * .title
+ * # title here
+ * %ul
+ * # content here
+ *
+ * Ex. 2: Block data
+ * .ui-box
+ * .title
+ * # title here
+ * .body
+ * # content here
*
*/
+
.ui-box {
background: #FFF;
margin-bottom: 20px;
border: 1px solid #DDD;
word-wrap: break-word;
- &.small-box {
- margin-bottom: 10px;
-
- .title {
- font-size: 13px;
- line-height: 30px;
-
- a {
- color: #666;
- &:hover {
- text-decoration: underline;
- }
- }
- }
+ img {
+ max-width: 100%;
}
- &.ui-box-show {
- color: #666;
- margin:20px 0;
- background: #FFF;
- box-shadow: inset 0 1px 0 #fff, 0 1px 5px #f1f1f1;
- @include linear-gradient(#fafafa, #f1f1f1);
-
- .control-group {
- margin-bottom: 0;
- }
- }
-
- &.ui-box-danger {
- .title {
- @include linear-gradient(#F26E5E, #bd362f);
- color: #fff;
- text-shadow: 0 1px 1px #900;
- font-weight: bold;
- }
- }
-
- img { max-width: 100%; }
-
pre {
code {
background: none !important;
}
}
- .ui-box-head,
- .ui-box-body,
- .ui-box-bottom {
- padding: 15px;
-
- .clearfix {
- margin: 0;
- }
- }
-
- .ui-box-head {
- .box-title {
- font-size: 18px;
- font-weight: normal;
- line-height: 28px;
- margin: 0;
- }
- h3 {
- margin: 0;
- }
- }
-
- .ui-box-body {
- border: none;
- background-color: #f5f5f5;
- border: none;
- border-top: 1px solid #eee;
- }
-
- .ui-box-bottom {
- border-top: 1px solid #eee;
- }
-
ul {
margin: 0;
+ padding: 0;
}
.title {
- @include bg-gray-gradient;
- border-bottom: 1px solid #CCC;
- color: #456;
+ background-color: #EEE;
+ border-bottom: 1px solid #DDD;
+ color: #666;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
padding: 0 10px;
@@ -145,6 +94,10 @@
}
}
+ .body {
+ padding: 10px;
+ }
+
&.padded {
h5, .title {
margin: -20px;
@@ -154,7 +107,7 @@
}
.row_title {
- font-weight: bold;
+ font-weight: 500;
color: #444;
&:hover {
color: #444;
@@ -176,13 +129,45 @@
}
}
+/*
+ * Small box
+ */
+.ui-box.ui-box-small {
+ margin-bottom: 10px;
+
+ .title {
+ font-size: 13px;
+ line-height: 30px;
+
+ a {
+ color: #666;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+}
+
+/*
+ * Danger box
+ */
+.ui-box.ui-box-danger {
+ background: #f7f7f7;
+ border: none;
+
+ .title {
+ background: #D65;
+ color: #fff;
+ text-shadow: none;
+ font-weight: 500;
+ }
+}
+
+/*
+ * Block under tw-bootstrap tabs
+ */
.tab-pane {
.ui-box {
margin: 3px 3px 25px 3px;
}
}
-
-.light-well {
- background: #f9f9f9;
- padding: 15px;
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss
deleted file mode 100644
index faf36b702c..0000000000
--- a/app/assets/stylesheets/gitlab_bootstrap.scss
+++ /dev/null
@@ -1,66 +0,0 @@
-/** Override bootstrap variables **/
-$baseFontSize: 13px !default;
-$baseLineHeight: 18px !default;
-
-/**
- * BOOTSTRAP
- */
-@import "bootstrap/variables";
-@import "bootstrap/mixins";
-@import "bootstrap/reset";
-@import "bootstrap/scaffolding";
-@import "bootstrap/grid";
-@import "bootstrap/layouts";
-@import "bootstrap/type";
-@import "bootstrap/code";
-@import "bootstrap/forms";
-@import "bootstrap/tables";
-@import "bootstrap/sprites";
-@import "bootstrap/dropdowns";
-@import "bootstrap/wells";
-@import "bootstrap/component-animations";
-@import "bootstrap/close";
-@import "bootstrap/button-groups";
-@import "bootstrap/alerts";
-@import "bootstrap/navs";
-@import "bootstrap/navbar";
-@import "bootstrap/breadcrumbs";
-@import "bootstrap/pagination";
-@import "bootstrap/pager";
-@import "bootstrap/modals";
-@import "bootstrap/tooltip";
-@import "bootstrap/popovers";
-@import "bootstrap/thumbnails";
-@import "bootstrap/media";
-@import "bootstrap/labels-badges";
-@import "bootstrap/progress-bars";
-@import "bootstrap/accordion";
-@import "bootstrap/carousel";
-@import "bootstrap/hero-unit";
-@import "bootstrap/utilities";
-@import "bootstrap/responsive-utilities";
-@import "bootstrap/responsive-1200px-min";
-
-/**
- * Font icons
- *
- */
-@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/avatar.scss";
-@import "gitlab_bootstrap/nav.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/lists.scss";
-@import "gitlab_bootstrap/forms.scss";
diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
deleted file mode 100644
index 9c547cc1ce..0000000000
--- a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
+++ /dev/null
@@ -1,148 +0,0 @@
-.btn {
- display: inline-block;
- padding: 6px 12px;
- margin-bottom: 0;
- font-size: 13px;
- line-height: $baseLineHeight;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid #BBB;
- color: $style_color;
- @include border-radius($baseBorderRadius);
- @include box-shadow(inset 0 1px 0 rgba(255,255,255,.2));
- @include linear-gradient(#f1f1f1, #e1e1e1);
- text-shadow: 0 1px 1px #FFF;
- text-decoration: none;
-
- &.hover,
- &:hover {
- color: $style_color;
- background: #f1f1f1;
- border-color: #AAA;
- text-decoration: none;
- @include linear-gradient(#fAfAfA, #f1f1f1);
- }
-
- &.focus,
- &:focus {
- text-decoration: none;
- @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
- }
-
- &.active,
- &:active {
- background-image: none;
- outline: 0;
- text-decoration: none;
- @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
- }
-
- &.disabled,
- &[disabled] {
- cursor: default;
- background-image: none;
- @include opacity(65);
- @include box-shadow(none);
- }
-
- &.btn-primary {
- color: #FFF;
- border-color: #189;
- text-shadow: 0 1px 1px #189;
- @include linear-gradient(#4AC, #289);
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #FFF;
- background: #389;
- }
- }
-
- &.btn-success {
- color: #FFF;
- border-color: #1A1;
- text-shadow: 0 1px 1px #FFF;
- text-shadow: 0 1px 1px #181;
- @include linear-gradient(#62C452, #51a351);
-
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #FFF;
- background: #2A2;
- }
- }
-
- &.btn-danger {
- color: #FFF;
- text-shadow: 0 1px 1px #811;
- border-color: #BD362F;
- @include linear-gradient(#EE5F5B, #BD362F);
-
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #FFF;
- background: #A22;
- }
- }
-
- &.btn-new {
- @extend .btn-success;
- }
-
- &.btn-create {
- @extend .wide;
- @extend .btn-success;
- }
-
- &.btn-save {
- @extend .wide;
- @extend .btn-primary;
- }
-
- &.btn-close,
- &.btn-remove {
- @extend .btn-danger;
- }
-
- &.btn-cancel {
- float: right;
- }
-
- &.wide {
- padding-left: 20px;
- padding-right: 20px;
- }
-
- &.btn-small {
- padding: 2px 10px;
- font-size: 12px;
- }
-
- &.btn-tiny {
- font-size: 11px;
- padding: 2px 6px;
- line-height: 16px;
- margin: 2px;
- }
-
- &.grouped {
- margin-right: 7px;
- float: left;
- }
-
- &.btn-block {
- width: 100%;
- margin: 0;
- padding: 6px 0;
- margin-bottom: 15px;
- }
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss
deleted file mode 100644
index 2ca60178df..0000000000
--- a/app/assets/stylesheets/gitlab_bootstrap/common.scss
+++ /dev/null
@@ -1,118 +0,0 @@
-/** COLORS **/
-.cgray { color: gray }
-.cred { color: #D12F19 }
-.cgreen { color: #4a2 }
-.cblue { color: #29A }
-.cblack { color: #111 }
-.cdark { color: #444 }
-.cwhite { color: #fff!important }
-.bgred { background: #F2DEDE!important }
-
-/** COMMON CLASSES **/
-.left { float:left }
-
-.prepend-top-10 { margin-top:10px }
-.prepend-top-20 { margin-top:20px }
-.prepend-left-10 { margin-left:10px }
-.prepend-left-20 { margin-left:20px }
-.append-right-10 { margin-right:10px }
-.append-right-20 { margin-right:20px }
-.append-bottom-10 { margin-bottom:10px }
-.append-bottom-20 { margin-bottom:20px }
-.inline { display: inline-block }
-
-.padded { padding:20px }
-.ipadded { padding:20px!important }
-.lborder { border-left:1px solid #eee }
-.underlined_link { text-decoration: underline; }
-.hint { font-style: italic; color: #999; }
-.light { color: #888 }
-.tiny { font-weight: normal }
-.vtop { vertical-align: top !important; }
-
-
-/** ALERT MESSAGES **/
-.alert.alert-disabled {
- background: #EEE;
- color: #777;
- border-color: #DDD;
-}
-
-/** HELPERS **/
-.nothing_here_message {
- text-align: center;
- padding: 20px;
- color: #666;
- font-weight: normal;
- font-size: 16px;
- line-height: 36px;
-}
-
-.slead {
- color: #666;
- font-size: 14px;
- margin-bottom: 12px;
- font-weight: normal;
- line-height: 24px;
-}
-
-
-.tab-content {
- overflow: visible;
-}
-
-@media (max-width: 1200px) {
- .only-wide {
- display: none;
- }
-}
-
-.pagination ul > li > a, .pagination ul > li >span {
- @include linear-gradient(#f1f1f1, #e1e1e1);
- color: #333;
- text-shadow: 0 1px 1px #FFF;
-}
-
-pre.well-pre {
- border: 1px solid #EEE;
- background: #f9f9f9;
- border-radius: 0;
- color: #555;
-}
-
-.input-append .btn.active, .input-prepend .btn.active {
- background: #CCC;
- border-color: #BBB;
- text-shadow: 0 1px 1px #fff;
- font-weight: bold;
- @include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
-}
-
-.label {
- padding: 2px 4px;
- font-size: 12px;
- font-style: normal;
- font-weight: normal;
-}
-
-/** Big Labels **/
-.state-label {
- font-size: 14px;
- padding: 5px 15px;
- text-align: center;
- float: right;
- position: relative;
- top: -5px;
- @include border-radius(4px);
- text-shadow: none;
-
- &.state-label-green {
- background: #4A4;
- color: #FFF;
- }
-
- &.state-label-red {
- background: #DA4E49;
- color: #FFF;
- }
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/forms.scss b/app/assets/stylesheets/gitlab_bootstrap/forms.scss
deleted file mode 100644
index a2612166c7..0000000000
--- a/app/assets/stylesheets/gitlab_bootstrap/forms.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-form {
- @extend .form-horizontal;
-
- label {
- @extend .control-label;
- }
-}
-
-input.input-xpadding,
-.add-on.input-xpadding {
- padding: 6px 10px;
-}
-
-.control-group {
- .control-label {
- padding-top: 6px;
- }
- .controls {
- input, textarea {
- padding: 6px 10px;
- }
-
- input[type="radio"], input[type="checkbox"] {
- margin-top: 6px;
- }
-
- .add-on {
- padding: 6px;
- }
- }
-}
-
-input[type='search'].search-text-input {
- background-image: url("icon-search.png");
- background-repeat: no-repeat;
- background-position: 10px;
- padding-left: 25px;
- @include border-radius(4px);
- border: 1px solid #ccc;
-}
-
-input[type='text'].danger {
- background: #F2DEDE!important;
- border-color: #D66;
- text-shadow: 0 1px 1px #fff
-}
-
-fieldset legend {
- font-size: 16px;
- margin-bottom: 10px;
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/nav.scss b/app/assets/stylesheets/gitlab_bootstrap/nav.scss
deleted file mode 100644
index aa4cb1ed5f..0000000000
--- a/app/assets/stylesheets/gitlab_bootstrap/nav.scss
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * nav-pills
- *
- */
-.nav-pills {
- .active a {
- background: $primary_color;
- }
-
- > li > a {
- @include border-radius(0);
- }
-
- &.nav-stacked {
- > li > a {
- border-left: 4px solid #EEE;
- padding: 12px;
- }
- > .active > a {
- border-color: $primary_color;
- border-radius: 0;
- background: #F1F1F1;
- color: $style_color;
- font-weight: bold;
- text-shadow: 0 1px 1px #fff;
- }
-
- &.nav-stacked-menu {
- background: #FAFAFA;
- li > a {
- padding: 16px;
- }
- }
- }
-
- &.nav-pills-small {
- > li > a {
- padding: 8px 12px;
- }
- }
-}
-
-.nav-pills > .active > a > i[class^="icon-"] { background: inherit; }
-
-
-
-/**
- * nav-tabs
- *
- */
-.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; }
-.nav.nav-tabs {
- li {
- > a {
- padding: 8px 20px;
- margin-right: 7px;
- line-height: 20px;
- border-color: #EEE;
- color: #888;
- border-bottom: 1px solid #ddd;
- .badge {
- background-color: #eee;
- color: #888;
- text-shadow: 0 1px 1px #fff;
- }
- i[class^="icon-"] {
- line-height: 14px;
- }
- }
- &.active {
- > a {
- border-color: #CCC;
- border-bottom: 1px solid #fff;
- color: #333;
- font-weight: bold;
- }
- }
- }
-
- &.nav-small-tabs > li > a { padding: 6px 9px; }
-}
-
-
-
-/**
- * fix to keep tooltips position in top navigation bar
- *
- */
-.navbar .nav > li {
- position: relative;
- white-space: nowrap;
-}
diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss
new file mode 100644
index 0000000000..8676f07b1a
--- /dev/null
+++ b/app/assets/stylesheets/gl_bootstrap.scss
@@ -0,0 +1,223 @@
+/*
+ * Twitter bootstrap with GitLab customizations/additions
+ *
+ * Some unused bootstrap compontents like panels are not included.
+ * Other components like tabs are modified to GitLab style.
+ *
+ */
+
+$font-size-base: 13px !default;
+$nav-pills-active-link-hover-bg: $bg_style_color;
+$pagination-active-bg: $bg_style_color;
+
+// Core variables and mixins
+@import "bootstrap/variables";
+@import "bootstrap/mixins";
+
+// Reset
+@import "bootstrap/normalize";
+@import "bootstrap/print";
+
+// Core CSS
+@import "bootstrap/scaffolding";
+@import "bootstrap/type";
+@import "bootstrap/code";
+@import "bootstrap/grid";
+@import "bootstrap/tables";
+@import "bootstrap/forms";
+
+// Components
+@import "bootstrap/component-animations";
+@import "bootstrap/dropdowns";
+@import "bootstrap/button-groups";
+@import "bootstrap/input-groups";
+@import "bootstrap/navs";
+@import "bootstrap/navbar";
+@import "bootstrap/breadcrumbs";
+@import "bootstrap/pagination";
+@import "bootstrap/pager";
+@import "bootstrap/labels";
+@import "bootstrap/badges";
+@import "bootstrap/jumbotron";
+@import "bootstrap/thumbnails";
+@import "bootstrap/alerts";
+@import "bootstrap/progress-bars";
+@import "bootstrap/list-group";
+@import "bootstrap/wells";
+@import "bootstrap/close";
+@import "bootstrap/panels";
+
+// Components w/ JavaScript
+@import "bootstrap/modals";
+@import "bootstrap/tooltip";
+@import "bootstrap/popovers";
+@import "bootstrap/carousel";
+
+// Utility classes
+.clearfix {
+ @include clearfix();
+}
+.center-block {
+ @include center-block();
+}
+.pull-right {
+ float: right !important;
+}
+.pull-left {
+ float: left !important;
+}
+.hide {
+ display: none;
+}
+.show {
+ display: block !important;
+}
+.invisible {
+ visibility: hidden;
+}
+.text-hide {
+ @include text-hide();
+}
+.hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+.affix {
+ position: fixed;
+}
+
+@import "bootstrap/responsive-utilities";
+
+// Labels
+.label {
+ padding: 2px 4px;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: normal;
+ display: inline-block;
+
+ &.label-gray {
+ background-color: #eee;
+ color: #999;
+ text-shadow: none;
+ }
+
+ &.label-inverse {
+ background-color: #333333;
+ }
+}
+
+// Nav tabs
+.nav.nav-tabs {
+ margin-bottom: 15px;
+
+ li {
+ > a {
+ padding: 8px 20px;
+ margin-right: 7px;
+ line-height: 20px;
+ border-color: #EEE;
+ color: #888;
+ border-bottom: 1px solid #ddd;
+ .badge {
+ background-color: #eee;
+ color: #888;
+ text-shadow: 0 1px 1px #fff;
+ }
+ i[class^="icon-"] {
+ line-height: 14px;
+ }
+ }
+ &.active {
+ > a {
+ border-color: #CCC;
+ border-bottom: 1px solid #fff;
+ color: #333;
+ font-weight: bold;
+ }
+ }
+ }
+
+ &.nav-small-tabs > li > a {
+ padding: 6px 9px;
+ }
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ color: #666;
+}
+
+.nav-small > li > a {
+ padding: 3px 5px;
+ font-size: 12px;
+}
+
+
+/*
+ * Callouts from Bootstrap3 docs
+ *
+ * Not quite alerts, but custom and helpful notes for folks reading the docs.
+ * Requires a base and modifier class.
+ */
+
+/* Common styles for all types */
+.bs-callout {
+ margin: 20px 0;
+ padding: 20px;
+ border-left: 3px solid #eee;
+ color: #666;
+ background: #f9f9f9;
+}
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.bs-callout p:last-child {
+ margin-bottom: 0;
+}
+
+/* Variations */
+.bs-callout-danger {
+ background-color: #fdf7f7;
+ border-color: #eed3d7;
+ color: #b94a48;
+}
+.bs-callout-warning {
+ background-color: #faf8f0;
+ border-color: #faebcc;
+ color: #8a6d3b;
+}
+.bs-callout-info {
+ background-color: #f4f8fa;
+ border-color: #bce8f1;
+ color: #34789a;
+}
+.bs-callout-success {
+ background-color: #dff0d8;
+ border-color: #5cA64d;
+ color: #3c763d;
+}
+
+// Breadcrumb
+ul.breadcrumb {
+ background: white;
+ border: none;
+ li {
+ display: inline;
+ text-shadow: 0 1px 0 white
+ }
+
+ a {
+ font-size: 16px;
+ }
+}
+
+/**
+ * fix to keep tooltips position in top navigation bar
+ *
+ */
+.navbar .nav > li {
+ position: relative;
+ white-space: nowrap;
+}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 129d33dcac..ca51da3fdd 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,71 +1,199 @@
-.dark .highlight {
+.dark {
+ background-color: #232323;
- background-color: #333;
-
- pre {
- background-color: #333;
- color: #eee;
+ .line.hll {
+ background: #558;
}
- .hll { display: block; background-color: darken($hover, 65%) }
- .c { color: #888888; font-style: italic } /* Comment */
- .err { color: #a61717; background-color: #e3d2d2 } /* Error */
- .k { color: #CDA869; font-weight: bold } /* Keyword */
- .kp { color: #CDA869; font-weight: bold } /* Keyword */
- .cm { color: #888888 } /* Comment.Multiline */
- .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
- .c1 { color: #888888 } /* Comment.Single */
- .cs { color: #cc0000; font-weight: bold; background-color: transparent } /* Comment.Special */
- .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
- .ge { font-style: italic } /* Generic.Emph */
- .gr { color: #aa0000 } /* Generic.Error */
- .gh { color: #303030 } /* Generic.Heading */
- .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
- .go { color: #888888 } /* Generic.Output */
- .gp { color: #555555 } /* Generic.Prompt */
- .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 */
- .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 */
- .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 */
- .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
- .sb { color: #dd2200; background-color: transparent; } /* Literal.String.Backtick */
- .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 */
- .bp { color: #D58 } /* Name.Builtin.Pseudo */
- .vc { color: #336699 } /* Name.Variable.Class */
- .vg { color: #dd7700 } /* Name.Variable.Global */
- .vi { color: cyan }
-}
+ .highlight{
+ border-left: 1px solid #444;
+ }
+ .no-highlight {
+ color: #DDD;
+ }
+
+ .line-numbers a {
+ color: #666;
+ }
+
+ pre {
+ background-color: #232323;
+ }
+
+ .hljs {
+ display: block;
+ background: #232323;
+ color: #E6E1DC;
+ }
+
+ .hljs-comment,
+ .hljs-template_comment,
+ .hljs-javadoc,
+ .hljs-shebang {
+ color: #BC9458;
+ font-style: italic;
+ }
+
+ .hljs-keyword,
+ .ruby .hljs-function .hljs-keyword,
+ .hljs-request,
+ .hljs-status,
+ .nginx .hljs-title,
+ .method,
+ .hljs-list .hljs-title {
+ color: #C26230;
+ }
+
+ .hljs-string,
+ .hljs-number,
+ .hljs-regexp,
+ .hljs-tag .hljs-value,
+ .hljs-cdata,
+ .hljs-filter .hljs-argument,
+ .hljs-attr_selector,
+ .apache .hljs-cbracket,
+ .hljs-date,
+ .tex .hljs-command,
+ .markdown .hljs-link_label {
+ color: #A5C261;
+ }
+
+ .hljs-subst {
+ color: #519F50;
+ }
+
+ .hljs-tag,
+ .hljs-tag .hljs-keyword,
+ .hljs-tag .hljs-title,
+ .hljs-doctype,
+ .hljs-sub .hljs-identifier,
+ .hljs-pi,
+ .input_number {
+ color: #E8BF6A;
+ }
+
+ .hljs-identifier {
+ color: #D0D0FF;
+ }
+
+ .hljs-class .hljs-title,
+ .haskell .hljs-type,
+ .smalltalk .hljs-class,
+ .hljs-javadoctag,
+ .hljs-yardoctag,
+ .hljs-phpdoc {
+ text-decoration: none;
+ }
+
+ .hljs-constant {
+ color: #DA4939;
+ }
+
+
+ .hljs-symbol,
+ .hljs-built_in,
+ .ruby .hljs-symbol .hljs-string,
+ .ruby .hljs-symbol .hljs-identifier,
+ .markdown .hljs-link_url,
+ .hljs-attribute {
+ color: #6D9CBE;
+ }
+
+ .markdown .hljs-link_url {
+ text-decoration: underline;
+ }
+
+
+
+ .hljs-params,
+ .hljs-variable,
+ .clojure .hljs-attribute {
+ color: #D0D0FF;
+ }
+
+ .css .hljs-tag,
+ .hljs-rules .hljs-property,
+ .hljs-pseudo,
+ .tex .hljs-special {
+ color: #CDA869;
+ }
+
+ .css .hljs-class {
+ color: #9B703F;
+ }
+
+ .hljs-rules .hljs-keyword {
+ color: #C5AF75;
+ }
+
+ .hljs-rules .hljs-value {
+ color: #CF6A4C;
+ }
+
+ .css .hljs-id {
+ color: #8B98AB;
+ }
+
+ .hljs-annotation,
+ .apache .hljs-sqbracket,
+ .nginx .hljs-built_in {
+ color: #9B859D;
+ }
+
+ .hljs-preprocessor,
+ .hljs-preprocessor *,
+ .hljs-pragma {
+ color: #8996A8 !important;
+ }
+
+ .hljs-hexcolor,
+ .css .hljs-value .hljs-number {
+ color: #A5C261;
+ }
+
+ .hljs-title,
+ .hljs-decorator,
+ .css .hljs-function {
+ color: #FFC66D;
+ }
+
+ .diff .hljs-header,
+ .hljs-chunk {
+ background-color: #2F33AB;
+ color: #E6E1DC;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .diff .hljs-change {
+ background-color: #4A410D;
+ color: #F8F8F8;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .hljs-addition {
+ background-color: #144212;
+ color: #E6E1DC;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .hljs-deletion {
+ background-color: #600;
+ color: #E6E1DC;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .coffeescript .javascript,
+ .javascript .xml,
+ .tex .hljs-formula,
+ .xml .javascript,
+ .xml .vbscript,
+ .xml .css,
+ .xml .hljs-cdata {
+ opacity: 0.7;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index c9709fa7f1..36bc5df2f4 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,89 +1,148 @@
-$monokai-fg: #f8f8f2;
-$monokai-comment: #75715e;
-$monokai-pink: #f92672;
-$monokai-blue: #66d9ef;
-$monokai-green: #a6e22e;
-$monokai-gold: #e6db74;
-$monokai-dark: #3b3a32;
-$monokai-purple: #ae81ff;
-
-.monokai .highlight {
-
+.monokai {
background-color: #272822;
+ .highlight{
+ border-left: 1px solid #444;
+ }
+
+ .line.hll {
+ background: #558;
+ }
+
+ .no-highlight {
+ color: #DDD;
+ }
+
+ .line-numbers a {
+ color: #666;
+ }
+
pre {
background-color: #272822;
- color: $monokai-fg;
+ color: #f8f8f2;
}
- .hll { background-color: darken($hover, 65%) }
- .c { color: $monokai-comment } /* Comment */
- .err { color: $monokai-fg } /* Error */
- .g { color: $monokai-fg } /* Generic */
- .k { color: $monokai-pink } /* Keyword */
- .l { color: $monokai-fg } /* Literal */
- .n { color: $monokai-blue } /* Name */
- .o { color: $monokai-fg } /* Operator */
- .x { color: $monokai-fg } /* Other */
- .p { color: $monokai-fg } /* Punctuation */
- .cm { color: $monokai-comment } /* Comment.Multiline */
- .cp { color: $monokai-comment } /* Comment.Preproc */
- .c1 { color: $monokai-comment } /* Comment.Single */
- .cs { color: $monokai-comment } /* Comment.Special */
- .gd { color: #8b0807 } /* Generic.Deleted */
- .ge { color: $monokai-fg; text-decoration: underline } /* Generic.Emph */
- .gr { color: $monokai-fg } /* Generic.Error */
- .gh { color: $monokai-fg; font-weight: bold } /* Generic.Heading */
- .gi { color: $monokai-fg; font-weight: bold; background-color: #46830c } /* Generic.Inserted */
- .go { color: $monokai-dark; background-color: #31322c } /* Generic.Output */
- .gp { color: $monokai-fg } /* Generic.Prompt */
- .gs { color: $monokai-fg } /* Generic.Strong */
- .gu { color: $monokai-fg; font-weight: bold } /* Generic.Subheading */
- .gt { color: #f8f8f0; background-color: $monokai-pink } /* Generic.Traceback */
- .kc { color: $monokai-purple } /* Keyword.Constant */
- .kd { color: $monokai-pink } /* Keyword.Declaration */
- .kn { color: $monokai-pink } /* Keyword.Namespace */
- .kp { color: $monokai-pink } /* Keyword.Pseudo */
- .kr { color: $monokai-pink } /* Keyword.Reserved */
- .kt { color: $monokai-fg } /* Keyword.Type */
- .ld { color: $monokai-fg } /* Literal.Date */
- .m { color: $monokai-purple } /* Literal.Number */
- .s { color: $monokai-gold } /* Literal.String */
- .na { color: $monokai-purple } /* Name.Attribute */
- .nb { color: $monokai-blue } /* Name.Builtin */
- .nc { color: $monokai-fg } /* Name.Class */
- .no { color: $monokai-fg } /* Name.Constant */
- .nd { color: $monokai-fg } /* Name.Decorator */
- .ni { color: $monokai-fg } /* Name.Entity */
- .ne { color: $monokai-fg } /* Name.Exception */
- .nf { color: $monokai-green } /* Name.Function */
- .nl { color: $monokai-gold } /* Name.Label */
- .nn { color: $monokai-fg } /* Name.Namespace */
- .nx { color: $monokai-fg } /* Name.Other */
- .nt { color: $monokai-pink } /* Name.Tag */
- .nv { color: $monokai-blue; font-style: italic } /* Name.Variable */
- .py { color: $monokai-fg } /* Name.Property */
- .ow { color: $monokai-pink } /* Operator.Word */
- .w { color: $monokai-fg } /* Text.Whitespace */
- .mf { color: $monokai-purple } /* Literal.Number.Float */
- .mh { color: $monokai-purple } /* Literal.Number.Hex */
- .mi { color: $monokai-purple } /* Literal.Number.Integer */
- .mo { color: $monokai-purple } /* Literal.Number.Oct */
- .sb { color: $monokai-gold } /* Literal.String.Backtick */
- .sc { color: $monokai-gold } /* Literal.String.Char */
- .sd { color: $monokai-gold } /* Literal.String.Doc */
- .s2 { color: $monokai-gold } /* Literal.String.Double */
- .se { color: $monokai-gold } /* Literal.String.Escape */
- .sh { color: $monokai-gold } /* Literal.String.Heredoc */
- .si { color: $monokai-gold } /* Literal.String.Interpol */
- .sx { color: $monokai-gold } /* Literal.String.Other */
- .sr { color: $monokai-gold } /* Literal.String.Regex */
- .s1 { color: $monokai-gold } /* Literal.String.Single */
- .ss { color: $monokai-gold } /* Literal.String.Symbol */
- .bp { color: $monokai-fg } /* Name.Builtin.Pseudo */
- .vc { color: $monokai-blue; font-style: italic } /* Name.Variable.Class */
- .vg { color: $monokai-blue; font-style: italic } /* Name.Variable.Global */
- .vi { color: $monokai-blue; font-style: italic } /* Name.Variable.Instance */
- .il { color: $monokai-purple } /* Literal.Number.Integer.Long */
-}
+ .hljs {
+ display: block;
+ background: #272822;
+ }
+ .hljs-tag,
+ .hljs-tag .hljs-title,
+ .hljs-keyword,
+ .hljs-literal,
+ .hljs-strong,
+ .hljs-change,
+ .hljs-winutils,
+ .hljs-flow,
+ .lisp .hljs-title,
+ .clojure .hljs-built_in,
+ .nginx .hljs-title,
+ .tex .hljs-special {
+ color: #F92672;
+ }
+
+ .hljs {
+ color: #DDD;
+ }
+
+ .hljs .hljs-constant,
+ .asciidoc .hljs-code {
+ color: #66D9EF;
+ }
+
+ .hljs-code,
+ .hljs-class .hljs-title,
+ .hljs-header {
+ color: white;
+ }
+
+ .hljs-link_label,
+ .hljs-attribute,
+ .hljs-symbol,
+ .hljs-symbol .hljs-string,
+ .hljs-value,
+ .hljs-regexp {
+ color: #BF79DB;
+ }
+
+ .hljs-link_url,
+ .hljs-tag .hljs-value,
+ .hljs-string,
+ .hljs-bullet,
+ .hljs-subst,
+ .hljs-title,
+ .hljs-emphasis,
+ .haskell .hljs-type,
+ .hljs-preprocessor,
+ .hljs-pragma,
+ .ruby .hljs-class .hljs-parent,
+ .hljs-built_in,
+ .sql .hljs-aggregate,
+ .django .hljs-template_tag,
+ .django .hljs-variable,
+ .smalltalk .hljs-class,
+ .hljs-javadoc,
+ .django .hljs-filter .hljs-argument,
+ .smalltalk .hljs-localvars,
+ .smalltalk .hljs-array,
+ .hljs-attr_selector,
+ .hljs-pseudo,
+ .hljs-addition,
+ .hljs-stream,
+ .hljs-envvar,
+ .apache .hljs-tag,
+ .apache .hljs-cbracket,
+ .tex .hljs-command,
+ .hljs-prompt {
+ color: #A6E22E;
+ }
+
+ .hljs-comment,
+ .java .hljs-annotation,
+ .smartquote,
+ .hljs-blockquote,
+ .hljs-horizontal_rule,
+ .python .hljs-decorator,
+ .hljs-template_comment,
+ .hljs-pi,
+ .hljs-doctype,
+ .hljs-deletion,
+ .hljs-shebang,
+ .apache .hljs-sqbracket,
+ .tex .hljs-formula {
+ color: #75715E;
+ }
+
+ .hljs-keyword,
+ .hljs-literal,
+ .css .hljs-id,
+ .hljs-phpdoc,
+ .hljs-title,
+ .hljs-header,
+ .haskell .hljs-type,
+ .vbscript .hljs-built_in,
+ .sql .hljs-aggregate,
+ .rsl .hljs-built_in,
+ .smalltalk .hljs-class,
+ .diff .hljs-header,
+ .hljs-chunk,
+ .hljs-winutils,
+ .bash .hljs-variable,
+ .apache .hljs-tag,
+ .tex .hljs-special,
+ .hljs-request,
+ .hljs-status {
+ font-weight: bold;
+ }
+
+ .coffeescript .javascript,
+ .javascript .xml,
+ .tex .hljs-formula,
+ .xml .javascript,
+ .xml .vbscript,
+ .xml .css,
+ .xml .hljs-cdata {
+ opacity: 0.5;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index cc82f39ac9..b9bec22518 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,80 +1,125 @@
-.solarized-dark .highlight {
-
+.solarized-dark {
background-color: #002B36;
-
+
+ .highlight{
+ border-left: 1px solid #113b46;
+ }
+
+ .line.hll {
+ background: #000;
+ }
+
+ .no-highlight {
+ color: #DDD;
+ }
+
pre {
background-color: #002B36;
color: #eee;
}
- .hll { background-color: #073642 }
- .c { color: #586E75 } /* Comment */
- .err { color: #93A1A1 } /* Error */
- .g { color: #93A1A1 } /* Generic */
- .k { color: #859900 } /* Keyword */
- .l { color: #93A1A1 } /* Literal */
- .n { color: #93A1A1 } /* Name */
- .o { color: #859900 } /* Operator */
- .x { color: #CB4B16 } /* Other */
- .p { color: #93A1A1 } /* Punctuation */
- .cm { color: #586E75 } /* Comment.Multiline */
- .cp { color: #859900 } /* Comment.Preproc */
- .c1 { color: #586E75 } /* Comment.Single */
- .cs { color: #859900 } /* Comment.Special */
- .gd { color: #2AA198 } /* Generic.Deleted */
- .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */
- .gr { color: #DC322F } /* Generic.Error */
- .gh { color: #CB4B16 } /* Generic.Heading */
- .gi { color: #859900 } /* Generic.Inserted */
- .go { color: #93A1A1 } /* Generic.Output */
- .gp { color: #93A1A1 } /* Generic.Prompt */
- .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */
- .gu { color: #CB4B16 } /* Generic.Subheading */
- .gt { color: #93A1A1 } /* Generic.Traceback */
- .kc { color: #CB4B16 } /* Keyword.Constant */
- .kd { color: #268BD2 } /* Keyword.Declaration */
- .kn { color: #859900 } /* Keyword.Namespace */
- .kp { color: #859900 } /* Keyword.Pseudo */
- .kr { color: #268BD2 } /* Keyword.Reserved */
- .kt { color: #DC322F } /* Keyword.Type */
- .ld { color: #93A1A1 } /* Literal.Date */
- .m { color: #2AA198 } /* Literal.Number */
- .s { color: #2AA198 } /* Literal.String */
- .na { color: #93A1A1 } /* Name.Attribute */
- .nb { color: #B58900 } /* Name.Builtin */
- .nc { color: #268BD2 } /* Name.Class */
- .no { color: #CB4B16 } /* Name.Constant */
- .nd { color: #268BD2 } /* Name.Decorator */
- .ni { color: #CB4B16 } /* Name.Entity */
- .ne { color: #CB4B16 } /* Name.Exception */
- .nf { color: #268BD2 } /* Name.Function */
- .nl { color: #93A1A1 } /* Name.Label */
- .nn { color: #93A1A1 } /* Name.Namespace */
- .nx { color: #93A1A1 } /* Name.Other */
- .py { color: #93A1A1 } /* Name.Property */
- .nt { color: #268BD2 } /* Name.Tag */
- .nv { color: #268BD2 } /* Name.Variable */
- .ow { color: #859900 } /* Operator.Word */
- .w { color: #93A1A1 } /* Text.Whitespace */
- .mf { color: #2AA198 } /* Literal.Number.Float */
- .mh { color: #2AA198 } /* Literal.Number.Hex */
- .mi { color: #2AA198 } /* Literal.Number.Integer */
- .mo { color: #2AA198 } /* Literal.Number.Oct */
- .sb { color: #586E75 } /* Literal.String.Backtick */
- .sc { color: #2AA198 } /* Literal.String.Char */
- .sd { color: #93A1A1 } /* Literal.String.Doc */
- .s2 { color: #2AA198 } /* Literal.String.Double */
- .se { color: #CB4B16 } /* Literal.String.Escape */
- .sh { color: #93A1A1 } /* Literal.String.Heredoc */
- .si { color: #2AA198 } /* Literal.String.Interpol */
- .sx { color: #2AA198 } /* Literal.String.Other */
- .sr { color: #DC322F } /* Literal.String.Regex */
- .s1 { color: #2AA198 } /* Literal.String.Single */
- .ss { color: #2AA198 } /* Literal.String.Symbol */
- .bp { color: #268BD2 } /* Name.Builtin.Pseudo */
- .vc { color: #268BD2 } /* Name.Variable.Class */
- .vg { color: #268BD2 } /* Name.Variable.Global */
- .vi { color: #268BD2 } /* Name.Variable.Instance */
- .il { color: #2AA198 } /* Literal.Number.Integer.Long */
-}
+ .line-numbers a {
+ color: #666;
+ }
+ .hljs {
+ display: block;
+ background: #002b36;
+ color: #839496;
+ }
+
+ .hljs-comment,
+ .hljs-template_comment,
+ .diff .hljs-header,
+ .hljs-doctype,
+ .hljs-pi,
+ .lisp .hljs-string,
+ .hljs-javadoc {
+ color: #586e75;
+ }
+
+ /* Solarized Green */
+ .hljs-keyword,
+ .hljs-winutils,
+ .method,
+ .hljs-addition,
+ .css .hljs-tag,
+ .hljs-request,
+ .hljs-status,
+ .nginx .hljs-title {
+ color: #859900;
+ }
+
+ /* Solarized Cyan */
+ .hljs-number,
+ .hljs-command,
+ .hljs-string,
+ .hljs-tag .hljs-value,
+ .hljs-rules .hljs-value,
+ .hljs-phpdoc,
+ .tex .hljs-formula,
+ .hljs-regexp,
+ .hljs-hexcolor,
+ .hljs-link_url {
+ color: #2aa198;
+ }
+
+ /* Solarized Blue */
+ .hljs-title,
+ .hljs-localvars,
+ .hljs-chunk,
+ .hljs-decorator,
+ .hljs-built_in,
+ .hljs-identifier,
+ .vhdl .hljs-literal,
+ .hljs-id,
+ .css .hljs-function {
+ color: #268bd2;
+ }
+
+ /* Solarized Yellow */
+ .hljs-attribute,
+ .hljs-variable,
+ .lisp .hljs-body,
+ .smalltalk .hljs-number,
+ .hljs-constant,
+ .hljs-class .hljs-title,
+ .hljs-parent,
+ .haskell .hljs-type,
+ .hljs-link_reference {
+ color: #b58900;
+ }
+
+ /* Solarized Orange */
+ .hljs-preprocessor,
+ .hljs-preprocessor .hljs-keyword,
+ .hljs-pragma,
+ .hljs-shebang,
+ .hljs-symbol,
+ .hljs-symbol .hljs-string,
+ .diff .hljs-change,
+ .hljs-special,
+ .hljs-attr_selector,
+ .hljs-subst,
+ .hljs-cdata,
+ .clojure .hljs-title,
+ .css .hljs-pseudo,
+ .hljs-header {
+ color: #cb4b16;
+ }
+
+ /* Solarized Red */
+ .hljs-deletion,
+ .hljs-important {
+ color: #dc322f;
+ }
+
+ /* Solarized Violet */
+ .hljs-link_label {
+ color: #6c71c4;
+ }
+
+ .tex .hljs-formula {
+ background: #073642;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index df127a7c49..c06bed3c21 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,72 +1,186 @@
-.white .highlight {
-
+.white {
background-color: #fff;
-
+
+ .line.hll {
+ background: #FFA;
+ }
+
+ .highlight{
+ border-left: 1px solid #eee;
+ }
+
pre {
background-color: #fff;
color: #333;
}
- .hll { display: block; background-color: $hover }
- .c { color: #888888; font-style: italic } /* Comment */
- .err { color: #a61717; background-color: #e3d2d2 } /* Error */
- .k { color: #000000; font-weight: bold } /* Keyword */
- .cm { color: #888888 } /* Comment.Multiline */
- .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
- .c1 { color: #888888 } /* Comment.Single */
- .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
- .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
- .ge { font-style: italic } /* Generic.Emph */
- .gr { color: #aa0000 } /* Generic.Error */
- .gh { color: #303030 } /* Generic.Heading */
- .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
- .go { color: #888888 } /* Generic.Output */
- .gp { color: #555555 } /* Generic.Prompt */
- .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 */
- .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 */
- .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 */
- .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
- .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
- .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 */
- .bp { color: #003388 } /* Name.Builtin.Pseudo */
- .vc { color: #336699 } /* Name.Variable.Class */
- .vg { color: #dd7700 } /* Name.Variable.Global */
- .vi { color: #3333bb }
+ .hljs {
+ background: #FFF;
+ }
+
+ .line-numbers a {
+ color: #999;
+ }
+
+ .hljs {
+ display: block;
+ background: #fff; color: black;
+ }
+
+ .hljs-comment,
+ .hljs-template_comment,
+ .hljs-javadoc,
+ .hljs-comment * {
+ color: #006a00;
+ }
+
+ .hljs-keyword,
+ .hljs-literal,
+ .nginx .hljs-title {
+ color: #aa0d91;
+ }
+ .method,
+ .hljs-list .hljs-title,
+ .hljs-tag .hljs-title,
+ .setting .hljs-value,
+ .hljs-winutils,
+ .tex .hljs-command,
+ .http .hljs-title,
+ .hljs-request,
+ .hljs-status {
+ color: #008;
+ }
+
+ .hljs-envvar,
+ .tex .hljs-special {
+ color: #660;
+ }
+
+ .hljs-string {
+ color: #c41a16;
+ }
+ .hljs-tag .hljs-value,
+ .hljs-cdata,
+ .hljs-filter .hljs-argument,
+ .hljs-attr_selector,
+ .apache .hljs-cbracket,
+ .hljs-date,
+ .hljs-regexp {
+ color: #080;
+ }
+
+ .hljs-sub .hljs-identifier,
+ .hljs-pi,
+ .hljs-tag,
+ .hljs-tag .hljs-keyword,
+ .hljs-decorator,
+ .ini .hljs-title,
+ .hljs-shebang,
+ .hljs-prompt,
+ .hljs-hexcolor,
+ .hljs-rules .hljs-value,
+ .hljs-symbol,
+ .hljs-symbol .hljs-string,
+ .hljs-number,
+ .css .hljs-function,
+ .clojure .hljs-title,
+ .clojure .hljs-built_in,
+ .hljs-function .hljs-title,
+ .coffeescript .hljs-attribute {
+ color: #1c00cf;
+ }
+
+ .hljs-class .hljs-title,
+ .haskell .hljs-type,
+ .smalltalk .hljs-class,
+ .hljs-javadoctag,
+ .hljs-yardoctag,
+ .hljs-phpdoc,
+ .hljs-typename,
+ .hljs-tag .hljs-attribute,
+ .hljs-doctype,
+ .hljs-class .hljs-id,
+ .hljs-built_in,
+ .setting,
+ .hljs-params,
+ .clojure .hljs-attribute {
+ color: #5c2699;
+ }
+
+ .hljs-variable {
+ color: #3f6e74;
+ }
+ .css .hljs-tag,
+ .hljs-rules .hljs-property,
+ .hljs-pseudo,
+ .hljs-subst {
+ color: #000;
+ }
+
+ .css .hljs-class,
+ .css .hljs-id {
+ color: #9B703F;
+ }
+
+ .hljs-value .hljs-important {
+ color: #ff7700;
+ font-weight: bold;
+ }
+
+ .hljs-rules .hljs-keyword {
+ color: #C5AF75;
+ }
+
+ .hljs-annotation,
+ .apache .hljs-sqbracket,
+ .nginx .hljs-built_in {
+ color: #9B859D;
+ }
+
+ .hljs-preprocessor,
+ .hljs-preprocessor *,
+ .hljs-pragma {
+ color: #643820;
+ }
+
+ .tex .hljs-formula {
+ background-color: #EEE;
+ font-style: italic;
+ }
+
+ .diff .hljs-header,
+ .hljs-chunk {
+ color: #808080;
+ font-weight: bold;
+ }
+
+ .diff .hljs-change {
+ background-color: #BCCFF9;
+ }
+
+ .hljs-addition {
+ background-color: #BAEEBA;
+ }
+
+ .hljs-deletion {
+ background-color: #FFC8BD;
+ }
+
+ .hljs-comment .hljs-yardoctag {
+ font-weight: bold;
+ }
+
+ .method .hljs-id {
+ color: #000;
+ }
}
.shadow {
@include box-shadow(0 5px 15px #000);
}
+
+.wiki, .note-body {
+ .highlight {
+ border: 1px solid #DDD;
+ }
+}
diff --git a/app/assets/stylesheets/jquery.ui.gitlab.css b/app/assets/stylesheets/jquery.ui.gitlab.css
deleted file mode 100644
index 5c51600ba6..0000000000
--- a/app/assets/stylesheets/jquery.ui.gitlab.css
+++ /dev/null
@@ -1,257 +0,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 #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/gitlab_bootstrap/fonts.scss b/app/assets/stylesheets/main/fonts.scss
similarity index 67%
rename from app/assets/stylesheets/gitlab_bootstrap/fonts.scss
rename to app/assets/stylesheets/main/fonts.scss
index 8cc9986415..d90274a0db 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/fonts.scss
+++ b/app/assets/stylesheets/main/fonts.scss
@@ -1,2 +1,3 @@
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
+$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/main/layout.scss
new file mode 100644
index 0000000000..e28da65c01
--- /dev/null
+++ b/app/assets/stylesheets/main/layout.scss
@@ -0,0 +1,18 @@
+html {
+ overflow-y: scroll;
+
+ &.touch .tooltip { display: none !important; }
+}
+
+body {
+ padding-bottom: 20px;
+}
+
+.container {
+ padding-top: 0;
+ z-index: 5;
+}
+
+.container .content {
+ margin: 0 0;
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss b/app/assets/stylesheets/main/mixins.scss
similarity index 59%
rename from app/assets/stylesheets/gitlab_bootstrap/mixins.scss
rename to app/assets/stylesheets/main/mixins.scss
index 8b975a12cf..8143cfa2c8 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/mixins.scss
+++ b/app/assets/stylesheets/main/mixins.scss
@@ -25,6 +25,7 @@
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: -ms-linear-gradient($from, $to);
background-image: -o-linear-gradient($from, $to);
}
@@ -40,28 +41,6 @@
* 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);
@@ -73,22 +52,44 @@
@mixin header-font {
color: $style_color;
- text-shadow: 0 1px 1px #FFF;
font-size: 16px;
- line-height: 40px;
+ line-height: 44px;
font-weight: normal;
}
@mixin md-typography {
+ font-size: 14px;
+ line-height: 1.6;
+
+ img {
+ max-width: 100%;
+ }
+
*:first-child {
margin-top: 0;
}
code { padding: 0 4px; }
- h1 { margin-top: 30px;}
- h2 { margin-top: 25px;}
- h3 { margin-top: 20px;}
- h4 { margin-top: 15px;}
+
+ h1 {
+ margin-top: 45px;
+ font-size: 2.5em;
+ }
+
+ h2 {
+ margin-top: 40px;
+ font-size: 2em;
+ }
+
+ h3 {
+ margin-top: 35px;
+ font-size: 1.5em;
+ }
+
+ h4 {
+ margin-top: 30px;
+ font-size: 1.2em;
+ }
blockquote p {
color: #888;
@@ -103,10 +104,20 @@
background: #EEE;
}
}
+
+ p > code {
+ font-size: inherit;
+ font-weight: inherit;
+ color: #555;
+ }
+
+ li {
+ line-height: 1.5;
+ }
}
@mixin page-title {
- color: $style_color;
+ color: #333;
font-size: 20px;
line-height: 1.5;
margin-top: 0px;
diff --git a/app/assets/stylesheets/gitlab_bootstrap/variables.scss b/app/assets/stylesheets/main/variables.scss
similarity index 72%
rename from app/assets/stylesheets/gitlab_bootstrap/variables.scss
rename to app/assets/stylesheets/main/variables.scss
index aeabe7ad2e..4b5fa0979b 100644
--- a/app/assets/stylesheets/gitlab_bootstrap/variables.scss
+++ b/app/assets/stylesheets/main/variables.scss
@@ -4,6 +4,8 @@
$primary_color: #2FA0BB;
$link_color: #3A89A3;
$style_color: #474D57;
+$bg_style_color: #2299BB;
+$list-group-active-bg: $bg_style_color;
$hover: #D9EDF7;
/**
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
new file mode 100644
index 0000000000..42dbf4d6ef
--- /dev/null
+++ b/app/assets/stylesheets/print.scss
@@ -0,0 +1,13 @@
+/* Generic print styles */
+header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
+.profiler-results {display: none;}
+
+/* Styles targeted specifically at printing files */
+.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
+.file-title {display: none;}
+.file-holder {border: none;}
+
+.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
+.wiki h1 {font-size: 30px;}
+.wiki h2 {font-size: 22px;}
+.wiki h3 {font-size: 18px; font-weight: bold; }
diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss
index e189fd27ac..a558633d11 100644
--- a/app/assets/stylesheets/sections/admin.scss
+++ b/app/assets/stylesheets/sections/admin.scss
@@ -2,7 +2,7 @@
* Admin area
*
*/
-.admin_dash {
+.admin-dashboard {
.data {
a {
h1 {
@@ -14,10 +14,29 @@
}
}
}
+
+ .str-truncated {
+ max-width: 60%;
+ }
}
.admin-filter form {
label { width: 110px; }
.controls { margin-left: 130px; }
.form-actions { padding-left: 130px; background: #fff }
+ .visibility-levels {
+ .controls {
+ margin-bottom: 9px;
+ }
+
+ i {
+ color: inherit;
+ }
+ }
+}
+
+.broadcast-messages {
+ .message {
+ line-height: 2;
+ }
}
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index 45a83cb608..855bb4ea01 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -10,323 +10,6 @@
}
}
-.file {
- border: 1px solid #CCC;
- margin-bottom: 1em;
-
- .header {
- @extend .clearfix;
- padding: 5px 5px 5px 10px;
- color: #555;
- 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);
- background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
-
- a{
- color: $style_color;
- }
-
- > span {
- font-family: $monospace_font;
- font-size: 14px;
- line-height: 30px;
- }
-
- a.view-file{
- font-weight: bold;
- }
-
- .commit-short-id{
- font-family: $monospace_font;
- font-size: smaller;
- }
-
- .file-mode{
- font-family: $monospace_font;
- }
- }
- .content {
- overflow: auto;
- overflow-y: hidden;
- background: #FFF;
- color: #333;
- font-size: 12px;
- .old{
- span.idiff{
- background-color: #FAA;
- }
- }
- .new{
- span.idiff{
- background-color: #AFA;
- }
- }
-
- table {
- width: 100%;
- font-family: $monospace_font;
- border: none;
- margin: 0px;
- padding: 0px;
- td {
- line-height: 18px;
- font-size: 12px;
- }
- }
- .old_line, .new_line {
- 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;
- @include user-select(none);
- a {
- float: left;
- width: 35px;
- font-weight: normal;
- color: #666;
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .line_holder {
- &.old .old_line,
- &.old .new_line {
- background: #FCC;
- border-color: #E7BABA;
- }
- &.new .old_line,
- &.new .new_line {
- background: #CFC;
- border-color: #B9ECB9;
- }
- }
- .line_content {
- display: block;
- white-space: pre;
- height: 18px;
- margin: 0px;
- padding: 0px 0.5em;
- border: none;
- &.new {
- background: #CFD;
- }
- &.old {
- background: #FDD;
- }
- &.matched {
- color: #ccc;
- background: #fafafa;
- }
- }
- }
- .image {
- background: #ddd;
- text-align: center;
- padding: 30px;
- .wrap{
- display: inline-block;
- }
-
- .frame {
- display: inline-block;
- background-color: #fff;
- line-height: 0;
- img{
- border: 1px solid #FFF;
- background: url('trans_bg.gif');
- }
- &.deleted {
- border: 1px solid $deleted;
- }
-
- &.added {
- border: 1px solid $added;
- }
- }
- .image-info{
- font-size: 12px;
- margin: 5px 0 0 0;
- color: grey;
- }
-
- .view.swipe{
- position: relative;
-
- .swipe-frame{
- display: block;
- margin: auto;
- position: relative;
- }
- .swipe-wrap{
- overflow: hidden;
- border-left: 1px solid #999;
- position: absolute;
- display: block;
- top: 13px;
- right: 7px;
- }
- .frame{
- top: 0;
- right: 0;
- position: absolute;
- &.deleted{
- margin: 0;
- display: block;
- top: 13px;
- right: 7px;
- }
- }
- .swipe-bar{
- display: block;
- height: 100%;
- width: 15px;
- z-index: 100;
- position: absolute;
- cursor: pointer;
- &:hover{
- .top-handle{
- background-position: -15px 3px;
- }
- .bottom-handle{
- background-position: -15px -11px;
- }
- };
- .top-handle{
- display: block;
- height: 14px;
- width: 15px;
- position: absolute;
- top: 0px;
- background: url('swipemode_sprites.gif') 0 3px no-repeat;
- }
- .bottom-handle{
- display: block;
- height: 14px;
- width: 15px;
- position: absolute;
- bottom: 0px;
- background: url('swipemode_sprites.gif') 0 -11px no-repeat;
- }
- }
- } //.view.swipe
- .view.onion-skin{
- .onion-skin-frame{
- display: block;
- margin: auto;
- position: relative;
- }
- .frame.added, .frame.deleted {
- position: absolute;
- display: block;
- top: 0px;
- left: 0px;
- }
- .controls{
- display: block;
- height: 14px;
- width: 300px;
- z-index: 100;
- position: absolute;
- bottom: 0px;
- left: 50%;
- margin-left: -150px;
-
- .drag-track{
- display: block;
- position: absolute;
- left: 12px;
- height: 10px;
- width: 276px;
- background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
- }
-
- .dragger {
- display: block;
- position: absolute;
- left: 0px;
- top: 0px;
- height: 14px;
- width: 14px;
- background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
- cursor: pointer;
- }
-
- .transparent {
- display: block;
- position: absolute;
- top: 2px;
- right: 0px;
- height: 10px;
- width: 10px;
- background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
- }
-
- .opaque {
- display: block;
- position: absolute;
- top: 2px;
- left: 0px;
- height: 10px;
- width: 10px;
- background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
- }
- }
- } //.view.onion-skin
- }
- .view-modes{
-
- padding: 10px;
- text-align: center;
-
- 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);
-
- ul, li{
- list-style: none;
- margin: 0;
- padding: 0;
- display: inline-block;
- }
-
- li{
- color: grey;
- border-left: 1px solid #c1c1c1;
- padding: 0 12px 0 16px;
- cursor: pointer;
- &:first-child{
- border-left: none;
- }
- &:hover{
- text-decoration: underline;
- }
- &.active{
- &:hover{
- text-decoration: none;
- }
- cursor: default;
- color: #333;
- }
- &.disabled{
- display: none;
- }
- }
- }
-}
-
/** COMMIT BLOCK **/
.commit-title{
display: block;
@@ -390,9 +73,9 @@
.commits-compare-switch{
- background: url("switch_icon.png") no-repeat center center;
- width: 22px;
- height: 22px;
+ background: image-url("switch_icon.png") no-repeat center center;
+ width: 32px;
+ height: 32px;
text-indent: -9999px;
float: left;
margin-right: 9px;
@@ -473,24 +156,48 @@ li.commit {
font-family: $monospace_font;
}
+ .str-truncated {
+ max-width: 70%;
+ }
+
.commit-row-message {
- color: #555;
- font-weight: bolder;
+ color: #333;
+ font-weight: 500;
&:hover {
color: #444;
text-decoration: underline;
}
}
+
+ .text-expander {
+ background: #eee;
+ color: #555;
+ padding: 0 5px;
+ cursor: pointer;
+ margin-left: 4px;
+ &:hover {
+ background-color: #ddd;
+ }
+ }
+ }
+
+ .commit-row-description {
+ font-size: 14px;
+ border-left: 1px solid #e5e5e5;
+ padding: 0 15px 0 7px;
+ margin: 5px 0 10px 5px;
+ display: none;
}
.commit-row-info {
+ color: #777;
+
a {
color: #777;
}
.committed_ago {
float: right;
- @extend .cgray;
}
}
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss
index 61043147fc..e70757e040 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/sections/dashboard.scss
@@ -1,11 +1,5 @@
.dashboard {
- @extend .row;
- .activities {
- }
-
.side {
- @extend .pull-right;
-
.ui-box {
margin: 0px;
box-shadow: none;
@@ -20,7 +14,7 @@
.search-text-input {
float:left;
- @extend .span2;
+ @extend .col-md-2;
}
.btn {
margin-left: 5px;
@@ -32,26 +26,27 @@
.dash-filter {
margin: 7px 0;
padding: 4px 6px;
- width: 202px;
+ width: 220px;
float: left;
+ height: inherit;
}
}
@media (max-width: 1200px) {
.dashboard .dash-filter {
- width: 132px;
+ width: 150px;
}
}
.dash-sidebar-tabs {
margin-bottom: 2px;
border: none;
- margin: 0;
+ margin: 0 !important;
li {
&.active {
a {
- @include linear-gradient(#f5f5f5, #eee);
+ background-color: #EEE;
border-bottom: 1px solid #EEE !important;
&:hover {
background: #eee;
@@ -66,37 +61,57 @@
}
.project-row, .group-row {
- padding: 10px 15px !important;
+ padding: 8px 12px !important;
+ font-size: 14px;
+ line-height: 24px;
- .namespace-name {
- color: #666;
- font-weight: bold;
+ a {
+ display: block;
}
.project-name, .group-name {
- font-size: 15px;
+ font-weight: 500;
}
.arrow {
float: right;
- padding: 10px 5px;
+ padding: 0px 5px;
margin: 0;
font-size: 20px;
color: #666;
}
.last-activity {
+ float: right;
+ font-size: 12px;
color: #AAA;
display: block;
- margin-top: 5px;
.date {
color: #777;
}
}
}
-.group-row {
- .arrow {
- padding: 2px 5px;
+.project-access-icon {
+ margin-left: 10px;
+ float: left;
+ margin-right: 15px;
+ font-size: 20px;
+ margin-bottom: 15px;
+ border: 1px solid #EEE;
+ padding: 8px 12px;
+ border-radius: 50px;
+ background: #f5f5f5;
+ text-align: center;
+
+ i {
+ color: #BBB;
}
}
+
+.dash-project-access-icon {
+ float: left;
+ margin-right: 3px;
+ color: #999;
+ width: 16px;
+}
diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss
new file mode 100644
index 0000000000..af44654d5d
--- /dev/null
+++ b/app/assets/stylesheets/sections/diff.scss
@@ -0,0 +1,332 @@
+.diff-file {
+ border: 1px solid #CCC;
+ margin-bottom: 1em;
+
+ .diff-header {
+ @extend .clearfix;
+ background: #EEE;
+ border-bottom: 1px solid #CCC;
+ padding: 5px 5px 5px 10px;
+ color: #555;
+
+ > span {
+ font-family: $monospace_font;
+ font-size: 14px;
+ line-height: 2;
+ }
+
+ .diff-btn-group {
+ float: right;
+
+ .btn {
+ background-color: #EEE;
+ color: #666;
+ font-weight: bolder;
+ }
+ }
+
+ .commit-short-id {
+ font-family: $monospace_font;
+ font-size: smaller;
+ }
+
+ .file-mode {
+ font-family: $monospace_font;
+ }
+ }
+ .diff-content {
+ overflow: auto;
+ overflow-y: hidden;
+ background: #FFF;
+ color: #333;
+ font-size: 12px;
+ .old {
+ span.idiff {
+ background-color: #FAA;
+ }
+ }
+ .new {
+ span.idiff {
+ background-color: #AFA;
+ }
+ }
+
+ table {
+ width: 100%;
+ font-family: $monospace_font;
+ border: none;
+ margin: 0px;
+ padding: 0px;
+ td {
+ line-height: 18px;
+ font-size: 12px;
+ }
+ }
+
+ tr.line_holder.parallel{
+ .old_line, .new_line, .diff_line {
+ min-width: 50px;
+ }
+
+ td.line_content.parallel{
+ width: 50%;
+ }
+ }
+
+ .old_line, .new_line, .diff_line {
+ margin: 0px;
+ padding: 0px;
+ border: none;
+ background: #F5F5F5;
+ color: #666;
+ padding: 0px 5px;
+ border-right: 1px solid #ccc;
+ text-align: right;
+ min-width: 35px;
+ max-width: 50px;
+ width: 35px;
+ @include user-select(none);
+ a {
+ float: left;
+ width: 35px;
+ font-weight: normal;
+ color: #666;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ &.new {
+ background: #CFD;
+ }
+ &.old {
+ background: #FDD;
+ }
+ }
+ .diff_line {
+ padding: 0;
+ }
+ .line_holder {
+ &.old .old_line,
+ &.old .new_line {
+ background: #FCC;
+ border-color: #E7BABA;
+ }
+ &.new .old_line,
+ &.new .new_line {
+ background: #CFC;
+ border-color: #B9ECB9;
+ }
+ }
+ .line_content {
+ display: block;
+ white-space: pre;
+ height: 18px;
+ margin: 0px;
+ padding: 0px 0.5em;
+ border: none;
+ &.new {
+ background: #CFD;
+ }
+ &.old {
+ background: #FDD;
+ }
+ &.matched {
+ color: #ccc;
+ background: #fafafa;
+ }
+ &.parallel {
+ display: table-cell;
+ }
+ }
+ }
+ .image {
+ background: #ddd;
+ text-align: center;
+ padding: 30px;
+ .wrap{
+ display: inline-block;
+ }
+
+ .frame {
+ display: inline-block;
+ background-color: #fff;
+ line-height: 0;
+ img{
+ border: 1px solid #FFF;
+ background: image-url('trans_bg.gif');
+ max-width: 100%;
+ }
+ &.deleted {
+ border: 1px solid $deleted;
+ }
+
+ &.added {
+ border: 1px solid $added;
+ }
+ }
+ .image-info{
+ font-size: 12px;
+ margin: 5px 0 0 0;
+ color: grey;
+ }
+
+ .view.swipe{
+ position: relative;
+
+ .swipe-frame{
+ display: block;
+ margin: auto;
+ position: relative;
+ }
+ .swipe-wrap{
+ overflow: hidden;
+ border-left: 1px solid #999;
+ position: absolute;
+ display: block;
+ top: 13px;
+ right: 7px;
+ }
+ .frame{
+ top: 0;
+ right: 0;
+ position: absolute;
+ &.deleted{
+ margin: 0;
+ display: block;
+ top: 13px;
+ right: 7px;
+ }
+ }
+ .swipe-bar{
+ display: block;
+ height: 100%;
+ width: 15px;
+ z-index: 100;
+ position: absolute;
+ cursor: pointer;
+ &:hover{
+ .top-handle{
+ background-position: -15px 3px;
+ }
+ .bottom-handle{
+ background-position: -15px -11px;
+ }
+ };
+ .top-handle{
+ display: block;
+ height: 14px;
+ width: 15px;
+ position: absolute;
+ top: 0px;
+ background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
+ }
+ .bottom-handle{
+ display: block;
+ height: 14px;
+ width: 15px;
+ position: absolute;
+ bottom: 0px;
+ background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
+ }
+ }
+ } //.view.swipe
+ .view.onion-skin{
+ .onion-skin-frame{
+ display: block;
+ margin: auto;
+ position: relative;
+ }
+ .frame.added, .frame.deleted {
+ position: absolute;
+ display: block;
+ top: 0px;
+ left: 0px;
+ }
+ .controls{
+ display: block;
+ height: 14px;
+ width: 300px;
+ z-index: 100;
+ position: absolute;
+ bottom: 0px;
+ left: 50%;
+ margin-left: -150px;
+
+ .drag-track{
+ display: block;
+ position: absolute;
+ left: 12px;
+ height: 10px;
+ width: 276px;
+ background: image-url('onion_skin_sprites.gif') -4px -20px repeat-x;
+ }
+
+ .dragger {
+ display: block;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ height: 14px;
+ width: 14px;
+ background: image-url('onion_skin_sprites.gif') 0px -34px repeat-x;
+ cursor: pointer;
+ }
+
+ .transparent {
+ display: block;
+ position: absolute;
+ top: 2px;
+ right: 0px;
+ height: 10px;
+ width: 10px;
+ background: image-url('onion_skin_sprites.gif') -2px 0px no-repeat;
+ }
+
+ .opaque {
+ display: block;
+ position: absolute;
+ top: 2px;
+ left: 0px;
+ height: 10px;
+ width: 10px;
+ background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
+ }
+ }
+ } //.view.onion-skin
+ }
+ .view-modes{
+ padding: 10px;
+ text-align: center;
+ background: #EEE;
+
+ ul, li{
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ }
+
+ li{
+ color: grey;
+ border-left: 1px solid #c1c1c1;
+ padding: 0 12px 0 16px;
+ cursor: pointer;
+ &:first-child{
+ border-left: none;
+ }
+ &:hover{
+ text-decoration: underline;
+ }
+ &.active{
+ &:hover{
+ text-decoration: none;
+ }
+ cursor: default;
+ color: #333;
+ }
+ &.disabled{
+ display: none;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/sections/editor.scss
index a71e543893..f62f46ee16 100644
--- a/app/assets/stylesheets/sections/editor.scss
+++ b/app/assets/stylesheets/sections/editor.scss
@@ -26,23 +26,9 @@
float: left;
}
- .commit-btn {
- @extend .save-btn;
- }
.message {
display: inline-block;
margin: 5px 8px 0 8px;
}
}
- .commit_message-group {
- margin-top: 20px;
-
- label {
- font-size: 16px;
- line-height: 20px;
- }
- textarea {
- @extend .span8;
- }
- }
}
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index 966282750d..b68f882220 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -37,12 +37,12 @@
&.event-inline {
.avatar {
- width: 16px;
- height: 16px;
+ position: relative;
+ top: -2px;
}
}
- padding: 14px 0px;
+ padding: 12px 0px;
border-bottom: 1px solid #eee;
.event-title {
color: #333;
@@ -63,6 +63,10 @@
color: #666;
margin-top: 5px;
+ .md {
+ font-size: 13px;
+ }
+
pre {
border: none;
background: #f9f9f9;
@@ -75,6 +79,7 @@
margin-top: 4px;
margin-left: 0px;
max-width: 200px;
+ float: none;
}
p:last-child {
@@ -112,6 +117,7 @@
&.commit {
background: transparent;
padding: 3px;
+ padding-left: 0;
border: none;
color: #666;
.commit-row-title {
@@ -121,6 +127,7 @@
&.commits-stat {
display: block;
padding: 3px;
+ padding-left: 0;
&:hover {
background: none;
@@ -147,7 +154,7 @@
float: left;
padding: 9px 6px;
font-size: 18px;
- width: 26px;
+ width: 40px;
@include border-radius(3px);
}
diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss
index 1e22d161bf..8a337a5e20 100644
--- a/app/assets/stylesheets/sections/graph.scss
+++ b/app/assets/stylesheets/sections/graph.scss
@@ -1,17 +1,16 @@
.project-network {
- border: 1px solid #aaa;
- padding: 1px;
+ border: 1px solid #CCC;
.tip {
color: #888;
font-size: 14px;
padding: 10px;
border-bottom: 1px solid #bbb;
- @include bg-gray-gradient;
+ background: #EEE;
}
.network-graph {
- background: #f1f1f1;
+ background: #FFF;
height: 500px;
overflow-y: scroll;
overflow-x: hidden;
diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/sections/groups.scss
new file mode 100644
index 0000000000..60ec79acad
--- /dev/null
+++ b/app/assets/stylesheets/sections/groups.scss
@@ -0,0 +1,9 @@
+.new-group-member-holder {
+ margin-top: 50px;
+ background: #f9f9f9;
+ padding-top: 20px;
+}
+
+.member-search-form {
+ float: left;
+}
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index bd72d08295..1adbdfd979 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -4,17 +4,23 @@
*/
header {
&.navbar-gitlab {
+ margin-bottom: 0;
+ min-height: 40px;
+
.navbar-inner {
- height: 40px;
- padding: 3px;
background: #F1F1F1;
+ border-bottom: 1px solid #DDD;
filter: none;
.nav > li > a {
color: $style_color;
- text-shadow: 0 1px 0 #fff;
font-size: 14px;
- padding: 10px;
+ line-height: 32px;
+ padding: 6px 10px;
+
+ &:hover, &:focus, &:active {
+ background: none;
+ }
}
/** NAV block with links and profile **/
@@ -22,6 +28,64 @@ header {
float: right;
margin-right: 0;
}
+
+ .navbar-toggle {
+ color: $style_color;
+ margin: 0 -15px 0 0;
+ padding: 10px;
+ border-radius: 0;
+
+ button i { font-size: 22px; }
+
+ &.collapsed { background-color: transparent !important;}
+
+ &:hover {
+ background-color: #EEE;
+ }
+ }
+ }
+
+ .turbolink-spinner {
+ font-size: 20px;
+ margin-right: 10px;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ border-width: 0;
+ font-size: 18px;
+
+ .app_logo { margin-left: -15px; }
+ .title {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ white-space: nowrap;
+ max-width: 70%;
+ }
+
+ .navbar-collapse {
+ padding-right: 0;
+ padding-left: 0;
+ }
+
+ .navbar-nav {
+ margin: 5px 0;
+
+ .visible-xs, .visable-sm {
+ display: table-cell !important;
+ }
+ }
+
+ li {
+ display: table-cell;
+ width: 1%;
+
+ a {
+ text-align: center;
+ font-size: 18px !important;
+ }
+ }
}
}
@@ -35,9 +99,6 @@ header {
.app_logo {
float: left;
margin-right: 9px;
- position: relative;
- top: -5px;
- padding-top: 5px;
a {
float: left;
@@ -46,10 +107,10 @@ header {
h1 {
margin: 0;
- background: url('logo-black.png') no-repeat center 1px;
- background-size: 38px;
+ background: image-url('logo-black.png') no-repeat center center;
+ background-size: 32px;
float: left;
- height: 40px;
+ height: 46px;
width: 40px;
@include header-font;
text-indent: -9999px;
@@ -65,7 +126,7 @@ header {
* Project / Area name
*
*/
- .project_name {
+ .title {
position: relative;
float: left;
margin: 0;
@@ -75,7 +136,7 @@ header {
.profile-pic {
position: relative;
- top: -4px;
+ top: -1px;
img {
width: 26px;
height: 26px;
@@ -91,21 +152,25 @@ header {
.search {
margin-right: 10px;
margin-left: 10px;
+ margin-top: 8px;
+
+ form {
+ margin: 0;
+ padding: 0;
+ }
.search-input {
- @extend .span3;
- background-image: url("icon-search.png");
+ background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
+ height: inherit;
+ padding: 4px 6px;
padding-left: 25px;
font-size: 13px;
@include border-radius(3px);
border: 1px solid #c6c6c6;
box-shadow: none;
@include transition(all 0.15s ease-in 0s);
- &:focus {
- @extend .span4;
- }
}
}
@@ -120,11 +185,13 @@ header {
background: #708090;
border-bottom: 1px solid #AAA;
+ .navbar-toggle { color: #fff; }
+
.nav > li > a {
color: #AAA;
- text-shadow: 0 1px 0 #444;
- &:hover {
+ &:hover, &:focus, &:active {
+ background: none;
color: #FFF;
}
}
@@ -152,22 +219,20 @@ header {
.app_logo {
a {
h1 {
- background: url('logo-white.png') no-repeat center 1px;
- background-size: 38px;
+ background: image-url('logo-white.png') no-repeat center center;
+ background-size: 32px;
color: #fff;
- text-shadow: 0 1px 1px #444;
}
}
}
- .project_name {
+ .title {
a {
- color: #BBB;
+ color: #FFF;
&:hover {
- color: #FFF;
+ text-decoration: underline;
}
}
color: #fff;
- text-shadow: 0 1px 1px #444;
}
}
@@ -181,12 +246,32 @@ header {
.separator {
float: left;
height: 46px;
- width: 1px;
+ width: 2px;
background: white;
border-left: 1px solid #DDD;
- margin-top: -3px;
margin-left: 10px;
margin-right: 10px;
}
}
+.search .search-input {
+ width: 300px;
+ &:focus {
+ width: 400px;
+ }
+}
+
+@media (max-width: 1200px) {
+ .search .search-input {
+ width: 200px;
+ &:focus {
+ width: 300px;
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ #nprogress .spinner {
+ right: 35px !important;
+ }
+}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index e384aebc76..02c9123178 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -14,8 +14,8 @@
.issue-check {
float: left;
- padding: 8px 0;
padding-right: 8px;
+ margin-bottom: 10px;
min-width: 15px;
}
@@ -38,13 +38,13 @@
}
}
-input.check_all_issues {
+.check-all-holder {
+ height: 32px;
float: left;
- padding: 0;
- margin: 0;
- margin-right: 10px;
- position: relative;
- top: 13px;
+ margin-right: 12px;
+ padding: 6px 10px;
+ border: 1px solid #ccc;
+ @include border-radius(4px);
}
.issues_content {
@@ -57,28 +57,11 @@ input.check_all_issues {
}
}
-.btn.close_issue {
- color: #B94A48;
- font-weight: bold;
- @include shade;
- &:hover {
- color: #B94A48;
- }
-}
-.btn.reopen_issue {
- color: #468847;
- font-weight: bold;
- @include shade;
- &:hover {
- color: #468847;
- }
-}
-
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
@media (min-width: 1200px) { .issues_filters select { width: 220px; } }
-@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } }
-@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } }
+@media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } }
+@media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } }
.issues-holder {
.issues_filters {
@@ -93,6 +76,13 @@ input.check_all_issues {
.update_selected_issues {
margin-left: 4px;
}
+
+ .select2-container .select2-choice {
+ height: 32px;
+ line-height: 28px;
+ color: #444 !important;
+ font-weight: 500;
+ }
}
}
@@ -103,3 +93,78 @@ input.check_all_issues {
.participants {
margin-bottom: 10px;
}
+
+.issues_bulk_update {
+ .select2-container {
+ text-shadow: none;
+ }
+}
+
+.issue-search-form {
+ margin: 0;
+ height: 24px;
+
+ .issue_search {
+ border: 1px solid #DDD !important;
+ background-color: #f4f4f4;
+ }
+}
+
+.issue-show-labels .label {
+ padding: 6px 10px;
+}
+
+form.edit-issue {
+ margin: 0;
+}
+
+.merge-request,
+.issue {
+ &.today {
+ background: #EFE;
+ border-color: #CEC;
+ }
+
+ &.closed {
+ background: #F5f5f5;
+ border-color: #E5E5E5;
+ }
+
+ &.merged {
+ background: #F5f5f5;
+ border-color: #E5E5E5;
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .issue-btn-group {
+ width: 100%;
+ margin-top: 5px;
+
+ .btn-group {
+ width: 100%;
+
+ ul {
+ width: 100%;
+ text-align: center;
+ }
+ }
+
+ .btn {
+ width: 100%;
+ margin-top: -1px;
+
+ &:first-child:not(:last-child) {
+ border-radius: 4px 4px 0 0;
+ }
+
+ &:not(:first-child):not(:last-child) {
+ border-radius: 0;
+ }
+
+ &:last-child:not(:first-child) {
+ border-radius: 0 0 4px 4px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss
index 33bef59c08..a78a9cd487 100644
--- a/app/assets/stylesheets/sections/login.scss
+++ b/app/assets/stylesheets/sections/login.scss
@@ -1,48 +1,57 @@
/* Login Page */
-body.login-page{
- .container > .content {
- padding-top: 20px;
+.login-page {
+ h1 {
+ font-size: 3em;
+ font-weight: 200;
}
-}
-
-.login-box{
- width: 304px;
- position: relative;
- @include border-radius(5px);
- margin: auto;
- padding: 20px;
- background: white;
-}
-
-.login-box .login-logo{
- margin: 10px 0 30px 0;
- display: block;
-}
-
-.login-box input.text{background-color: #f1f1f1; font-size: 16px; @include border-radius(0); padding: 14px 10px; width: 280px}
-
-.login-box input.text.top{
- @include border-radius(5px 5px 0 0);
- margin-bottom: 0px;
-}
-
-.login-box input.text.bottom{
- @include border-radius(0 0 5px 5px);
- border-top: 0;
- margin-bottom: 20px;
-}
-
-.login-box input.text.middle{
- border-top: 0;
- margin-bottom:0px;
-}
-
-.login-box a.forgot{float: right; padding-top: 6px}
-
-.remember_me {
- text-align: left;
-
- input {
- margin: 2px;
+
+ .login-box{
+ max-width: 304px;
+ position: relative;
+ @include border-radius(5px);
+ margin: auto;
+ background: white;
+ }
+
+ .login-logo{
+ margin: 10px 0 30px 0;
+ display: block;
+ }
+
+ .form-control {
+ background-color: #f1f1f1;
+ font-size: 16px;
+ padding: 14px 10px;
+ width: 100%;
+ height: auto;
+
+ &.top {
+ @include border-radius(5px 5px 0 0);
+ margin-bottom: 0px;
+ }
+
+ &.bottom {
+ @include border-radius(0 0 5px 5px);
+ border-top: 0;
+ margin-bottom: 20px;
+ }
+
+ &.middle {
+ border-top: 0;
+ margin-bottom:0px;
+ @include border-radius(0);
+ }
+ }
+
+ .login-box a.forgot {
+ float: right;
+ padding-top: 6px
+ }
+
+ .devise-errors {
+ h2 {
+ font-size: 14px;
+ color: #a00;
+ }
}
}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index aa61cae4b9..2d9a5e4bbe 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -4,10 +4,6 @@
*
*/
.automerge_widget {
- &.can_be_merged {
- background: #DFF0D8;
- }
-
form {
margin-bottom: 0;
.clearfix {
@@ -15,32 +11,12 @@
}
}
- .accept_group {
- float: left;
- border: 1px solid #ADA;
- padding: 2px;
- @include border-radius(5px);
- background: #CEB;
-
- .accept_merge_request {
- font-size: 13px;
- float: left;
- }
- .remove_branch_holder {
- margin-left: 20px;
- margin-right: 10px;
- float: left;
- }
+ .accept-group {
label {
- color: #444;
- text-align: left
+ margin: 5px;
+ margin-left: 20px;
}
}
-
-
- .how_to_merge_link {
- @extend .primary;
- }
}
.merge-request .nav-tabs{
@@ -53,17 +29,11 @@
}
}
-.merge-in-progress {
- @extend .padded;
- @extend .append-bottom-10;
-}
-
.mr_source_commit,
.mr_target_commit {
.commit {
margin: 0;
- padding: 0;
- padding: 5px 0;
+ padding: 2px 0;
list-style: none;
&:hover {
background: none;
@@ -73,10 +43,9 @@
.label-branch {
@include border-radius(4px);
- padding: 3px 4px;
+ padding: 2px 4px;
border: none;
- font-size: 14px;
- background: #474D57;
+ background: #555;
color: #fff;
font-family: $monospace_font;
font-weight: normal;
@@ -104,15 +73,49 @@
.merge-request-info {
color: #999;
+
+ .merge-request-labels {
+ display: inline-block;
+ }
}
}
}
.merge-request-angle {
text-align: center;
- margin: 0;
+ margin: 0 auto;
+ font-size: 2em;
+ line-height: 1.1;
}
.merge-request-form-info {
- padding: 15px 0;
+ padding-top: 15px;
+}
+
+// hide mr close link for inline diff comment form
+.diff-file .close-mr-link,
+.diff-file .reopen-mr-link {
+ display: none;
+}
+
+.mr-state-widget {
+ @include border-radius(0px);
+
+ .panel-heading {
+ @include border-radius(0px);
+ }
+
+ .panel-body {
+ h4 {
+ margin-top: 0px;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.merge-request-show-labels .label {
+ padding: 6px 10px;
}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index 54263523e8..88f16a2159 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -6,6 +6,7 @@
border-bottom: 1px solid #E1E1E1;
ul {
+ padding: 0;
margin: auto;
height: 40px;
overflow: hidden;
@@ -34,9 +35,8 @@
width: 1%;
&.active {
a {
- color: $style_color;
- font-weight: bolder;
-
+ color: #333;
+ font-weight: bold;
&:after {
content: '';
display: block;
@@ -45,7 +45,7 @@
left: 50%;
width: 0;
height: 0;
- border-color: transparent transparent #777 transparent;
+ border-color: transparent transparent #333 transparent;
border-style: solid;
border-width: 6px;
margin-left: -6px;
@@ -55,7 +55,20 @@
&:hover {
a {
- color: $style_color;
+ color: $link_color;
+ &:after {
+ content: '';
+ display: block;
+ position: relative;
+ bottom: 8px;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-color: transparent transparent #29b transparent;
+ border-style: solid;
+ border-width: 6px;
+ margin-left: -6px;
+ }
}
}
@@ -72,7 +85,7 @@
a {
display: block;
text-align: center;
- font-weight: normal;
+ font-weight: 500;
height: 38px;
line-height: 34px;
color: #777;
@@ -82,4 +95,38 @@
padding-top: 2px;
}
}
+
+ @media (max-width: $screen-xs-max) {
+ font-size: 18px;
+ margin: 0;
+
+ max-height: none;
+
+ &, .container {
+ padding: 0;
+ border-top: 0;
+ }
+
+ ul {
+ height: auto;
+
+ li {
+ display: list-item;
+ width: auto;
+ padding: 5px 0;
+
+ &.active {
+ background-color: $primary_color;
+
+ a {
+ color: #fff;
+ font-weight: normal;
+ text-shadow: none;
+
+ &:after { display: none; }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 94b9ca3b18..7e56781f56 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -2,7 +2,7 @@
* Notes
*/
-@-webkit-keyframes target-note {
+@-webkit-keyframes targe3-note {
from { background:#fffff0; }
50% { background:#ffffd3; }
to { background:#fffff0; }
@@ -27,11 +27,15 @@ ul.notes {
.discussion-last-update,
.note-last-update {
- font-style: italic;
+ &:before {
+ content: "\00b7";
+ }
+ font-size: 13px;
}
.author {
- color: $style_color;
+ color: #555;
font-weight: bold;
+ font-size: 14px;
&:hover {
color: $primary_color;
}
@@ -47,13 +51,12 @@ ul.notes {
.discussion-body {
margin-left: 50px;
- .file,
+ .diff-file,
.discussion-hidden,
.notes {
- @extend .borders;
background-color: #F9F9F9;
}
- .file .notes {
+ .diff-file .notes {
/* reset */
background: inherit;
border: none;
@@ -87,18 +90,13 @@ ul.notes {
}
.attachment {
font-size: 14px;
- margin-top: -20px;
}
.note-body {
@include md-typography;
- margin-left: 45px;
-
- .highlight {
- @include border-radius(4px);
- }
+ margin-left: 43px;
}
.note-header {
- padding-bottom: 5px;
+ padding-bottom: 3px;
}
}
@@ -118,10 +116,10 @@ ul.notes {
}
}
-.file .notes_holder {
- font-family: $sansFontFamily;
+.diff-file .notes_holder {
font-size: 13px;
line-height: 18px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
td {
border: 1px solid #ddd;
@@ -130,11 +128,18 @@ ul.notes {
&.notes_line {
text-align: center;
padding: 10px 0;
+ background: #eee;
+ }
+ &.notes_line2 {
+ text-align: center;
+ padding: 10px 0;
+ border-left: 1px solid #ddd !important;
}
&.notes_content {
- background-color: $white;
+ background-color: #fff;
border-width: 1px 0;
padding-top: 0;
+ vertical-align: top;
li {
padding: 5px;
@@ -182,20 +187,19 @@ ul.notes {
}
}
}
-.file .note .note-actions {
+.diff-file .note .note-actions {
right: 0;
top: 0;
}
-
/**
* Line note button on the side of diffs
*/
-.file tr.line_holder {
+.diff-file tr.line_holder {
.add-diff-note {
- background: url("diff_note_add.png") no-repeat left 0;
+ background: image-url("diff_note_add.png") no-repeat left 0;
height: 22px;
margin-left: -65px;
position: absolute;
@@ -233,30 +237,33 @@ ul.notes {
.reply-btn {
@extend .btn-primary;
}
-.file .content tr.line_holder:hover {
- &> td.line_content {
- background: $hover !important;
- border-color: darken($hover, 10%) !important;
+.diff-file .diff-content {
+ tr.line_holder:hover {
+ &> td.line_content {
+ background: $hover !important;
+ border-color: darken($hover, 10%) !important;
+ }
+ &> td.new_line,
+ &> td.old_line {
+ background: darken($hover, 4%) !important;
+ border-color: darken($hover, 10%) !important;
+ }
}
- &> td.new_line,
- &> td.old_line {
- background: darken($hover, 4%) !important;
- border-color: darken($hover, 10%) !important;
+
+ tr.line_holder:hover > td .line_note_link {
+ opacity: 1.0;
+ filter: alpha(opacity=100);
}
}
-.file .content tr.line_holder:hover > td .line_note_link {
- opacity: 1.0;
- filter: alpha(opacity=100);
-}
-.file,
+.diff-file,
.discussion {
.new_note {
- margin: 8px 5px 8px 0;
+ margin: 0;
+ border: none;
}
}
.new_note {
display: none;
-
.buttons {
float: left;
margin-top: 8px;
@@ -270,10 +277,9 @@ ul.notes {
// preview/edit buttons
> a {
- font-size: 24px;
- padding: 4px;
position: absolute;
- right: 10px;
+ right: 5px;
+ bottom: -60px;
}
.note_preview {
background: #f5f5f5;
@@ -287,7 +293,7 @@ ul.notes {
box-shadow: none;
font-size: 14px;
height: 80px;
- width: 98.6%;
+ width: 100%;
}
}
}
@@ -298,18 +304,17 @@ ul.notes {
}
.note-image-attach {
- @extend .span4;
+ @extend .col-md-4;
@extend .thumbnail;
margin-left: 45px;
+ float: none;
}
.common-note-form {
margin: 0;
- height: 140px;
background: #F9F9F9;
padding: 3px;
- padding-bottom: 25px;
border: 1px solid #DDD;
}
@@ -320,7 +325,7 @@ ul.notes {
padding: 0 5px;
.note-form-option {
- margin-top: 10px;
+ margin-top: 8px;
margin-left: 30px;
@extend .pull-left;
}
@@ -338,7 +343,7 @@ ul.notes {
box-shadow: none;
font-size: 14px;
height: 80px;
- width: 98.6%;
+ width: 100%;
}
.form-actions {
@@ -358,3 +363,7 @@ ul.notes {
.js-note-attachment-delete {
display: none;
}
+
+.parallel-comment {
+ padding: 6px;
+}
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 21f4ed0577..67aaa36938 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -1,7 +1,6 @@
.update-notifications {
- margin-bottom: 0;
- label {
- margin-bottom: 0;
+ .radio-inline {
+ margin-right: 9%;
}
}
@@ -17,7 +16,7 @@
legend {
border: none;
- margin: 0;
+ margin-bottom: 10px;
}
}
}
@@ -42,3 +41,87 @@
margin-right: 12px;
}
+.profile-avatar-form-option {
+ hr {
+ margin: 10px 0;
+ }
+}
+
+.user-show-username {
+ font-weight: 200;
+ color: #666;
+}
+
+/*
+ * Appearance settings
+ *
+ */
+.themes_opts {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .prev {
+ @extend .thumbnail;
+ height: 30px;
+ width: 175px;
+ margin-bottom: 10px;
+
+ &.classic {
+ background: #31363e;
+ }
+
+ &.default {
+ background: #f1f1f1;
+ }
+
+ &.modern {
+ background: #009871;
+ }
+
+ &.gray {
+ background: #373737;
+ }
+
+ &.violet {
+ background: #548;
+ }
+ }
+ }
+}
+
+.code_highlight_opts {
+ margin-top: 10px;
+
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .prev {
+ @extend .thumbnail;
+ height: 151px;
+ width: 220px;
+ margin-bottom: 10px;
+ }
+ }
+}
+
+.profile-groups-avatars {
+ margin: 0 5px 10px 0;
+
+ img {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+.global-notifications-form .level-title {
+ font-size: 15px;
+ color: #333;
+ font-weight: bold;
+}
+
+.notification-icon-holder {
+ width: 20px;
+ float: left;
+}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index 0e5f99d4b7..5757858a1c 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -16,12 +16,18 @@
.project-home-panel {
border-bottom: 1px solid #DDD;
- padding-bottom: 30px;
+ padding-bottom: 15px;
margin-bottom: 30px;
+ &.empty-project {
+ border-bottom: 0px;
+ padding-bottom: 15px;
+ margin-bottom: 0px;
+ }
+
.project-home-title {
font-size: 18px;
- color: #777;
+ color: #444;
margin: 0;
line-height: 32px;
}
@@ -34,40 +40,52 @@
.project-home-desc {
float: left;
- color: #999;
+ color: #777;
+ margin-bottom: 10px;
}
.project-home-links {
float: right;
a {
margin-left: 10px;
+ font-weight: 500;
}
}
}
- .public-label {
- font-size: 14px;
+ .visibility-level-label {
+ font-size: 17px;
background: #f1f1f1;
- padding: 8px 10px;
border-radius: 4px;
- margin-left: 10px;
color: #888;
+ position: absolute;
+ margin-left: -55px;
text-shadow: 0 1px 1px #FFF;
+ width: 40px;
+ text-align: center;
+ padding: 6px;
+
+ i {
+ color: inherit;
+ }
}
}
.git-clone-holder {
- float: right;
- border: 1px solid #E1E1E1;
- @include border-radius(4px);
+ .project-home-dropdown + & {
+ margin-right: 45px;
+ }
+
+ .btn,
+ .form-control {
+ border: 1px solid #E1E1E1;
+ box-shadow: none;
+ padding: 6px 9px;
+ }
.btn {
- margin-left: 3px;
- border: none;
background: none;
- box-shadow: none;
color: #29b;
- padding: 6px;
&.active {
color: #333;
@@ -75,33 +93,39 @@
}
}
- input[type="text"] {
- margin-left: 2px;
- border: none;
- border-radius: 0;
- border-left: 1px solid #E1E1E1;
+ .form-control {
+ cursor: auto;
@extend .monospace;
- box-shadow: none;
background: #FAFAFA;
- padding: 6px 10px;
+ width: 100%;
}
}
-.project-public-holder {
- .help-inline {
- padding-top: 7px;
+.project-visibility-level-holder {
+ .radio {
+ margin-bottom: 10px;
+
+ i {
+ margin: 0 3px;
+ font-size: 20px;
+ }
+
+ .option-title {
+ font-weight: bold;
+ display: inline-block;
+ }
+
+ .option-descr {
+ margin-left: 24px;
+ color: #666;
+ }
}
}
.save-project-loader {
- img {
- margin-top: 50px;
- margin-bottom: 50px;
- }
- h3 {
- @extend .page-title;
- }
-
+ margin-top: 50px;
+ margin-bottom: 50px;
+ color: #555;
}
ul.nav.nav-projects-tabs {
@@ -130,7 +154,8 @@ ul.nav.nav-projects-tabs {
margin: 0px;
}
-.my-projects {
+.my-projects,
+.public-projects {
li {
.project-info {
margin-bottom: 10px;
@@ -166,3 +191,44 @@ ul.nav.nav-projects-tabs {
color: #777;
}
}
+
+.project-side {
+ .btn-block {
+ background-image: none;
+ .btn,
+ &.btn,
+ &.btn-group ul.dropdown-menu {
+ background-color: #F1f1f1;
+ border-color: #EEE;
+ &:hover {
+ background-color: #eee;
+ border-color: #DDD;
+ }
+ }
+ &.btn-group-justified {
+ .btn {
+ width: 100%;
+ }
+ .dropdown-toggle {
+ width: 26px;
+ }
+ }
+ ul {
+ width: 100%;
+ }
+ }
+ .project-fork-icon {
+ float: left;
+ font-size: 26px;
+ margin-right: 10px;
+ line-height: 1.5;
+ }
+}
+
+.transfer-project .select2-container {
+ min-width: 200px;
+}
+
+.deploy-project-label {
+ margin: 1px;
+}
diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/sections/snippets.scss
index e67ca794f2..84404b6ee3 100644
--- a/app/assets/stylesheets/sections/snippets.scss
+++ b/app/assets/stylesheets/sections/snippets.scss
@@ -1,14 +1,3 @@
-.snippet.file-holder {
- .file-title {
- .snippet-file-name {
- padding: 4px 10px;
- position: relative;
- top: -4px;
- left: -4px;
- }
- }
-}
-
.my-snippets li:first-child {
h4 { margin-top: 0; }
padding-top: 0;
diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/sections/themes.scss
index cd1aa2b011..e69de29bb2 100644
--- a/app/assets/stylesheets/sections/themes.scss
+++ b/app/assets/stylesheets/sections/themes.scss
@@ -1,53 +0,0 @@
-.themes_opts {
- padding-left: 20px;
-
- label {
- width: 175px;
- margin-right: 40px;
-
- .prev {
- @extend .thumbnail;
- height: 30px;
- width: 175px;
- margin-bottom: 10px;
-
- &.classic {
- background: #31363e;
- }
-
- &.default {
- background: #f1f1f1;
- }
-
- &.modern {
- background: #345;
- }
-
- &.gray {
- background: #373737;
- }
-
- &.violet {
- background: #547;
- }
- }
- }
-}
-
-.code_highlight_opts {
- padding-left: 20px;
-
- label {
- width: 220px;
- margin-right: 40px;
-
- .prev {
- @extend .thumbnail;
- height: 151px;
- width: 220px;
- margin-bottom: 10px;
- }
- }
-}
-
-
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss
index 96f2a5711f..86e2a51641 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/sections/tree.scss
@@ -24,10 +24,10 @@
th {
font-weight: normal;
font-size: 15px;
- border-bottom: 1px solid #CCC;
+ border-bottom: 1px solid #CCC !important;
}
td {
- border-color: #F1F1F1;
+ border-color: #F1F1F1 !important;
}
&:hover {
td {
@@ -49,6 +49,7 @@
.tree-item {
.tree-item-file-name {
+ max-width: 320px;
vertical-align: middle;
a {
&:hover {
@@ -61,6 +62,14 @@
top:-1px;
}
}
+
+ .tree_commit {
+ max-width: 320px;
+ }
+
+ .tree_time_ago {
+ min-width: 135px;
+ }
}
.tree_author {
@@ -111,5 +120,36 @@
.tree-ref-holder {
float: left;
- margin-top: 5px;
+ margin-top: 8px;
}
+
+.readme-holder {
+ border-top: 1px dashed #CCC;
+ padding-top: 10px;
+
+ .readme-file-title {
+ font-size: 14px;
+ margin-bottom: 20px;
+ color: #777;
+ }
+}
+
+.blob-commit-info {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ margin-bottom: 10px;
+
+ .commit {
+ .commit-row-title {
+ font-size: 13px;
+
+ .commit-row-message {
+ font-weight: normal;
+ color: #555;
+ }
+ }
+ }
+}
+
+#modal-remove-blob > .modal-dialog { width: 850px; }
diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss
index 49489babab..d683e33e1f 100644
--- a/app/assets/stylesheets/sections/votes.scss
+++ b/app/assets/stylesheets/sections/votes.scss
@@ -36,3 +36,14 @@
display: inline-block;
margin: 0 8px;
}
+
+.votes-holder {
+ float: right;
+ width: 250px;
+
+ @media (max-width: $screen-xs-max) {
+ width: 100%;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/sections/wall.scss b/app/assets/stylesheets/sections/wall.scss
index d6ac08fcf6..3705afdb87 100644
--- a/app/assets/stylesheets/sections/wall.scss
+++ b/app/assets/stylesheets/sections/wall.scss
@@ -1,6 +1,6 @@
.wall-page {
.wall-note-form {
- @extend .span12;
+ @extend .col-md-12;
margin: 0;
height: 140px;
diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/sections/wiki.scss
index ed3a432ded..dfaeba41cf 100644
--- a/app/assets/stylesheets/sections/wiki.scss
+++ b/app/assets/stylesheets/sections/wiki.scss
@@ -1,4 +1,4 @@
-h3.page-title .edit-wiki-header {
+.title .edit-wiki-header {
width: 780px;
margin-left: auto;
margin-right: auto;
diff --git a/app/assets/stylesheets/selects.scss b/app/assets/stylesheets/selects.scss
deleted file mode 100644
index 09ae57b669..0000000000
--- a/app/assets/stylesheets/selects.scss
+++ /dev/null
@@ -1,153 +0,0 @@
-/* CHZN reset few styles */
-.chosen-container-single .chosen-single {
- background: #FFF;
- border: 1px solid #bbb;
- box-shadow: none;
-}
-.chosen-container-active .chosen-single {
- background: #fff;
-}
-
-.ajax-users-select {
- width: 400px;
-
- &.input-large {
- width: 210px;
- }
-}
-
-.user-result {
- .user-image {
- float: left;
- }
- .user-name {
- }
- .user-username {
- color: #999;
- }
-}
-
-/** Branch/tag selector **/
-.project-refs-form {
- margin: 0;
- span {
- background:none !important;
- position:static !important;
- width:auto !important;
- height:auto !important;
- }
-}
-.project-refs-select {
- width: 120px;
-}
-
-.project-refs-form .chosen-container {
- position: relative;
- top: 0;
- left: 0;
- margin-right: 10px;
-
- .chosen-drop {
- min-width: 400px;
- .chosen-results {
- max-height: 300px;
- }
- .chosen-search input {
- min-width: 365px;
- }
- }
-}
-
-/** Fix for Search Dropdown Border **/
-.chosen-container {
- min-width: 100px;
-
- .chosen-search {
- input:focus {
- @include box-shadow(none);
- }
- }
-
- .chosen-drop {
- margin: 7px 0;
- min-width: 200px;
- border: 1px solid #bbb;
- @include border-radius(0);
-
- .chosen-results {
- margin-top: 5px;
- max-height: 300px;
-
- .group-result {
- color: $style_color;
- border-bottom: 1px solid #EEE;
- padding: 8px;
- }
- .active-result {
- @include border-radius(0);
-
- &.highlighted {
- background: $hover;
- color: $style_color;
- }
- &.result-selected {
- background: #EEE;
- border-left: 4px solid #CCC;
- }
- }
- }
-
- .chosen-search {
- @include bg-gray-gradient;
- input {
- min-width: 165px;
- border-color: #CCC;
- }
- }
- }
-}
-
-.chosen-container .chosen-single,
-.chosen-container.chosen-with-drop .chosen-single {
- @include bg-light-gray-gradient;
-
- div {
- background: transparent;
- border-left: none;
- }
-
- span {
- font-weight: normal;
- }
-}
-
-/** Select2 styling **/
-.select2-container .select2-choice {
- background: #f1f1f1;
- background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, whitesmoke), to(#e1e1e1));
- background-image: -webkit-linear-gradient(whitesmoke 6.6%, #e1e1e1);
- background-image: -moz-linear-gradient(whitesmoke 6.6%, #e1e1e1);
- background-image: -o-linear-gradient(whitesmoke 6.6%, #e1e1e1);
-}
-
-.select2-container .select2-choice div {
- border: none;
- background: none;
-}
-
-.select2-drop {
- padding-top: 8px;
-}
-
-.select2-no-results, .select2-searching {
- padding: 7px;
- color: #666;
-}
-
-.chosen-container .chosen-single div b {
- background-position-y: 0px !important;
-}
-
-.chosen-container .chosen-drop .chosen-search input {
- background-position-y: -24px !important;
-}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
index 1fd54ff18a..a08f3ff3d4 100644
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ b/app/assets/stylesheets/themes/ui_color.scss
@@ -16,18 +16,28 @@
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #547;
- border-bottom: 1px solid #435;
- .app_logo {
+ background: #548;
+ border-bottom: 1px solid #436;
+ .app_logo, .navbar-toggle {
&:hover {
- background-color: #435;
+ background-color: #436;
}
}
.separator {
- background: #435;
- border-left: 1px solid #658;
+ background: #436;
+ border-left: 1px solid #659;
+ }
+ .nav > li > a {
+ color: #98C;
+ }
+ .search-input {
+ border-color: #98C;
}
}
}
}
+
+ .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
+ background: #659;
+ }
}
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
index 41c08c840e..959febad6f 100644
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ b/app/assets/stylesheets/themes/ui_gray.scss
@@ -18,7 +18,7 @@
.navbar-inner {
background: #373737;
border-bottom: 1px solid #272727;
- .app_logo {
+ .app_logo, .navbar-toggle {
&:hover {
background-color: #272727;
}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
index a2b8c21ea1..9af5adbf10 100644
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ b/app/assets/stylesheets/themes/ui_mars.scss
@@ -18,17 +18,22 @@
.navbar-inner {
background: #474D57;
border-bottom: 1px solid #373D47;
- .app_logo {
+ .app_logo, .navbar-toggle {
&:hover {
background-color: #373D47;
}
}
+ .separator {
+ background: #373D47;
+ border-left: 1px solid #575D67;
+ }
+ .nav > li > a {
+ color: #979DA7;
+ }
+ .search-input {
+ border-color: #979DA7;
+ }
}
}
-
- .separator {
- background: #31363E;
- border-left: 1px solid #666;
- }
}
}
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
index 6173757082..67616a4a10 100644
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ b/app/assets/stylesheets/themes/ui_modern.scss
@@ -16,18 +16,28 @@
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
- background: #345;
- border-bottom: 1px solid #234;
- .app_logo {
+ background: #00AC7E;
+ border-bottom: 1px solid #00AC7E;
+ .app_logo, .navbar-toggle {
&:hover {
- background-color: #234;
+ background-color: #009C6E;
}
}
.separator {
- background: #234;
- border-left: 1px solid #456;
+ background: #009C6F;
+ border-left: 1px solid #10BC8E;
+ }
+ .nav > li > a {
+ color: #ADC;
+ }
+ .search-input {
+ border-color: #7fd5be;
}
}
}
}
+
+ .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
+ background: #00AC7E;
+ }
}
diff --git a/app/contexts/commit_load_context.rb b/app/contexts/commit_load_context.rb
deleted file mode 100644
index 0c684976b6..0000000000
--- a/app/contexts/commit_load_context.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-class CommitLoadContext < BaseContext
- def execute
- result = {
- commit: nil,
- suppress_diff: false,
- line_notes: [],
- notes_count: 0,
- note: nil,
- status: :ok
- }
-
- commit = project.repository.commit(params[:id])
-
- if commit
- line_notes = project.notes.for_commit_id(commit.id).inline
-
- result[:commit] = commit
- result[:note] = project.build_commit_note(commit)
- result[:line_notes] = line_notes
- result[:notes_count] = project.notes.for_commit_id(commit.id).count
- result[:branches] = project.repository.branch_names_contains(commit.id)
-
- begin
- result[:suppress_diff] = true if commit.diff_suppress? && !params[:force_show_diff]
- result[:force_suppress_diff] = commit.diff_force_suppress?
- rescue Grit::Git::GitTimeout
- result[:suppress_diff] = true
- result[:status] = :huge_commit
- end
- end
-
- result
- end
-end
diff --git a/app/contexts/filter_context.rb b/app/contexts/filter_context.rb
deleted file mode 100644
index 2607b12b4a..0000000000
--- a/app/contexts/filter_context.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class FilterContext
- attr_accessor :items, :params
-
- def initialize(items, params)
- @items = items
- @params = params
- end
-
- def execute
- apply_filter(items)
- end
-
- def apply_filter items
- if params[:project_id].present?
- items = items.of_projects(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/contexts/issues/list_context.rb b/app/contexts/issues/list_context.rb
deleted file mode 100644
index da2eed0e25..0000000000
--- a/app/contexts/issues/list_context.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Issues
- class ListContext < BaseContext
- attr_accessor :issues
-
- def execute
- @issues = @project.issues
-
- @issues = case params[:state]
- when 'all' then @issues
- when 'closed' then @issues.closed
- else @issues.opened
- end
-
- @issues = case params[:scope]
- when 'assigned-to-me' then @issues.assigned_to(current_user)
- when 'created-by-me' then @issues.authored(current_user)
- else @issues
- end
-
- @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
- @issues = @issues.includes(:author, :project)
-
- # Filter by specific assignee_id (or lack thereof)?
- if params[:assignee_id].present?
- @issues = @issues.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
- end
-
- # Filter by specific milestone_id (or lack thereof)?
- if params[:milestone_id].present?
- @issues = @issues.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
- end
-
- @issues
- end
- end
-end
diff --git a/app/contexts/merge_requests_load_context.rb b/app/contexts/merge_requests_load_context.rb
deleted file mode 100644
index c3408db6e1..0000000000
--- a/app/contexts/merge_requests_load_context.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# Build collection of Merge Requests
-# based on filtering passed via params for @project
-class MergeRequestsLoadContext < BaseContext
- def execute
- merge_requests = @project.merge_requests
-
- merge_requests = case params[:state]
- when 'all' then merge_requests
- when 'closed' then merge_requests.closed
- else merge_requests.opened
- end
-
- merge_requests = case params[:scope]
- when 'assigned-to-me' then merge_requests.assigned_to(current_user)
- when 'created-by-me' then merge_requests.authored(current_user)
- else merge_requests
- end
-
-
- merge_requests = merge_requests.page(params[:page]).per(20)
- merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc")
-
- # Filter by specific assignee_id (or lack thereof)?
- if params[:assignee_id].present?
- merge_requests = merge_requests.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
- end
-
- # Filter by specific milestone_id (or lack thereof)?
- if params[:milestone_id].present?
- merge_requests = merge_requests.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
- end
-
- merge_requests
- end
-end
diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb
deleted file mode 100644
index 234e9ac3cd..0000000000
--- a/app/contexts/notes/load_context.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module Notes
- class LoadContext < BaseContext
- def execute
- target_type = params[:target_type]
- target_id = params[:target_id]
-
-
- @notes = case target_type
- when "commit"
- project.notes.for_commit_id(target_id).not_inline.fresh
- when "issue"
- project.issues.find(target_id).notes.inc_author.fresh
- when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
- when "snippet"
- project.snippets.find(target_id).notes.fresh
- end
- end
- end
-end
diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb
deleted file mode 100644
index 40385fa65b..0000000000
--- a/app/contexts/projects/update_context.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Projects
- class UpdateContext < BaseContext
- def execute(role = :default)
- params[:project].delete(:namespace_id)
- params[:project].delete(:public) unless can?(current_user, :change_public_mode, project)
- project.update_attributes(params[:project], as: role)
- end
- end
-end
diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb
deleted file mode 100644
index fa41da6823..0000000000
--- a/app/contexts/search_context.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-class SearchContext
- attr_accessor :project_ids, :params
-
- def initialize(project_ids, params)
- @project_ids, @params = project_ids, params.dup
- end
-
- def execute
- query = params[:search]
- query = Shellwords.shellescape(query) if query.present?
-
- return result unless query.present?
- result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20)
-
- # Search inside single project
- single_project_search(Project.where(id: project_ids), query)
- result
- end
-
- def single_project_search(projects, query)
- project = projects.first if projects.length == 1
-
- if params[:search_code].present?
- result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
- else
- result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20)
- result[:issues] = Issue.where(project_id: project_ids).where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20)
- result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project_ids).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20)
- result[:wiki_pages] = []
- end
- end
-
- def result
- @result ||= {
- projects: [],
- merge_requests: [],
- issues: [],
- notes: [],
- wiki_pages: [],
- blobs: []
- }
- end
-end
diff --git a/app/contexts/test_hook_context.rb b/app/contexts/test_hook_context.rb
deleted file mode 100644
index 63eda6c7d0..0000000000
--- a/app/contexts/test_hook_context.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class TestHookContext < BaseContext
- def execute
- hook = project.hooks.find(params[:id])
- data = GitPushService.new.sample_data(project, current_user)
- hook.execute(data)
- end
-end
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 159ab2027c..4c1d0df411 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,5 +1,6 @@
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
- @sidekiq_processes = `ps -U #{Settings.gitlab.user} -o euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep`
+ ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
+ @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
new file mode 100644
index 0000000000..9a70ef9d19
--- /dev/null
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -0,0 +1,32 @@
+class Admin::BroadcastMessagesController < Admin::ApplicationController
+ before_filter :broadcast_messages
+
+ def index
+ @broadcast_message = BroadcastMessage.new
+ end
+
+ def create
+ @broadcast_message = BroadcastMessage.new(params[:broadcast_message])
+
+ if @broadcast_message.save
+ redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.'
+ else
+ render :index
+ end
+ end
+
+ def destroy
+ BroadcastMessage.find(params[:id]).destroy
+
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js { render nothing: true }
+ end
+ end
+
+ protected
+
+ def broadcast_messages
+ @broadcast_messages ||= BroadcastMessage.order("starts_at DESC").page(params[:page])
+ end
+end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 3c80b6503f..be19139c9b 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -2,5 +2,6 @@ class Admin::DashboardController < Admin::ApplicationController
def index
@projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10)
+ @groups = Group.order("created_at DESC").limit(10)
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 89b395786b..4bb3cf07da 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -52,6 +52,6 @@ class Admin::GroupsController < Admin::ApplicationController
private
def group
- @group = Group.find_by_path(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 088174fd3b..92ef596337 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,21 +1,32 @@
class Admin::ProjectsController < Admin::ApplicationController
- before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
+ before_filter :project, only: [:show, :transfer]
+ before_filter :group, only: [:show, :transfer]
+ before_filter :repository, only: [:show, :transfer]
def index
owner_id = params[:owner_id]
- user = User.find_by_id(owner_id)
+ user = User.find_by(id: owner_id)
- @projects = user ? user.owned_projects : Project.scoped
- @projects = @projects.where(public: true) if params[:public_only].present?
+ @projects = user ? user.owned_projects : Project.all
+ @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.search(params[:name]) if params[:name].present?
+ @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end
def show
- @repository = @project.repository
- @group = @project.group
+ end
+
+ def transfer
+ result = ::Projects::TransferService.new(@project, current_user, project: params).execute(:admin)
+
+ if result
+ redirect_to [:admin, @project]
+ else
+ render :show
+ end
end
protected
@@ -26,4 +37,12 @@ class Admin::ProjectsController < Admin::ApplicationController
@project = Project.find_with_namespace(id)
@project || render_404
end
+
+ def group
+ @group ||= project.group
+ end
+
+ def repository
+ @repository ||= project.repository
+ end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index c9875b9632..5b06af79d5 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -2,8 +2,7 @@ class Admin::UsersController < Admin::ApplicationController
before_filter :user, only: [:show, :edit, :update, :destroy]
def index
- @users = User.scoped
- @users = @users.filter(params[:filter])
+ @users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
@users = @users.alphabetically.page(params[:page])
end
@@ -48,7 +47,7 @@ class Admin::UsersController < Admin::ApplicationController
@user.admin = (admin && admin.to_i > 0)
@user.created_by_id = current_user.id
@user.generate_password
- @user.confirm!
+ @user.skip_confirmation!
respond_to do |format|
if @user.save
@@ -69,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController
params[:user].delete(:password_confirmation)
end
- user.admin = (admin && admin.to_i > 0)
+ if admin.present?
+ user.admin = !admin.to_i.zero?
+ end
respond_to do |format|
if user.update_attributes(params[:user], as: :admin)
@@ -101,6 +102,6 @@ class Admin::UsersController < Admin::ApplicationController
protected
def user
- @user ||= User.find_by_username!(params[:id])
+ @user ||= User.find_by!(username: params[:id])
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cfa3cac5e8..2730e9942e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,12 +1,17 @@
+require 'gon'
+
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :reject_blocked!
before_filter :check_password_expiration
around_filter :set_current_user_for_thread
before_filter :add_abilities
+ before_filter :ldap_security_check
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
+ before_filter :configure_permitted_parameters, if: :devise_controller?
+ before_filter :require_email, unless: :devise_controller?
protect_from_forgery
@@ -81,6 +86,9 @@ class ApplicationController < ActionController::Base
if @project and can?(current_user, :read_project, @project)
@project
+ elsif current_user.nil?
+ @project = nil
+ authenticate_user!
else
@project = nil
render_404 and return
@@ -102,13 +110,18 @@ class ApplicationController < ActionController::Base
end
def authorize_code_access!
- return access_denied! unless can?(current_user, :download_code, project) or project.public?
+ return access_denied! unless can?(current_user, :download_code, project)
end
def authorize_push!
return access_denied! unless can?(current_user, :push_code, project)
end
+ def authorize_labels!
+ # Labels should be accessible for issues and/or merge requests
+ authorize_read_issue! || authorize_read_merge_request!
+ end
+
def access_denied!
render "errors/access_denied", layout: "errors", status: 404
end
@@ -129,12 +142,12 @@ class ApplicationController < ActionController::Base
end
end
- def render_404
- render file: Rails.root.join("public", "404"), layout: false, status: "404"
+ def render_403
+ head :forbidden
end
- def render_403
- render file: Rails.root.join("public", "403"), layout: false, status: "403"
+ def render_404
+ render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
def require_non_empty_project
@@ -148,12 +161,14 @@ class ApplicationController < ActionController::Base
end
def dev_tools
- Rack::MiniProfiler.authorize_request
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
+ headers['X-UA-Compatible'] = 'IE=edge'
+ headers['X-Content-Type-Options'] = 'nosniff'
+ headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https
end
def add_gon_variables
@@ -162,6 +177,7 @@ class ApplicationController < ActionController::Base
gon.api_token = current_user.private_token if current_user
gon.gravatar_url = request.ssl? || Gitlab.config.gitlab.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
+ gon.gravatar_enabled = Gitlab.config.gravatar.enabled
end
def check_password_expiration
@@ -170,8 +186,64 @@ class ApplicationController < ActionController::Base
end
end
+ def ldap_security_check
+ if current_user && current_user.requires_ldap_check?
+ gitlab_ldap_access do |access|
+ if access.allowed?(current_user)
+ current_user.last_credential_check_at = Time.now
+ current_user.save
+ else
+ sign_out current_user
+ flash[:alert] = "Access denied for your LDAP account."
+ redirect_to new_user_session_path
+ end
+ end
+ end
+ end
+
def event_filter
filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
+
+ def gitlab_ldap_access(&block)
+ Gitlab::LDAP::Access.open { |access| block.call(access) }
+ end
+
+ # JSON for infinite scroll via Pager object
+ def pager_json(partial, count)
+ html = render_to_string(
+ partial,
+ layout: false,
+ formats: [:html]
+ )
+
+ render json: {
+ html: html,
+ count: count
+ }
+ end
+
+ def view_to_html_string(partial)
+ render_to_string(
+ partial,
+ layout: false,
+ formats: [:html]
+ )
+ end
+
+ def configure_permitted_parameters
+ devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) }
+ devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) }
+ end
+
+ def hexdigest(string)
+ Digest::SHA1.hexdigest string
+ end
+
+ def require_email
+ if current_user && current_user.temp_oauth_email?
+ redirect_to profile_path, notice: 'Please complete your profile with email address' and return
+ end
+ end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index ac31938443..233b91680f 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,6 +3,8 @@ class DashboardController < ApplicationController
before_filter :load_projects, except: [:projects]
before_filter :event_filter, only: :show
+ before_filter :default_filter, only: [:issues, :merge_requests]
+
def show
# Fetch only 30 projects.
@@ -20,9 +22,11 @@ class DashboardController < ApplicationController
@last_push = current_user.recent_push
+ @publicish_project_count = Project.publicish(current_user).count
+
respond_to do |format|
format.html
- format.js
+ format.json { pager_json("events/_events", @events.count) }
format.atom { render layout: false }
end
end
@@ -39,29 +43,27 @@ class DashboardController < ApplicationController
current_user.authorized_projects
end
- @projects = @projects.where(namespace_id: Group.find_by_name(params[:group])) if params[:group].present?
- @projects = @projects.includes(:namespace).sorted_by_activity
+ @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
+ @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
+ @projects = @projects.includes(:namespace)
+ @projects = @projects.tagged_with(params[:label]) if params[:label].present?
+ @projects = @projects.sort(@sort = params[:sort])
+ @projects = @projects.page(params[:page]).per(30)
@labels = current_user.authorized_projects.tags_on(:labels)
@groups = current_user.authorized_groups
-
- @projects = @projects.tagged_with(params[:label]) if params[:label].present?
- @projects = @projects.page(params[:page]).per(30)
end
- # Get authored or assigned open merge requests
def merge_requests
- @merge_requests = current_user.cared_merge_requests
- @merge_requests = FilterContext.new(@merge_requests, params).execute
- @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
+ @merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = @merge_requests.preload(:author, :target_project)
end
- # Get only assigned issues
def issues
- @issues = current_user.assigned_issues
- @issues = FilterContext.new(@issues, params).execute
- @issues = @issues.recent.page(params[:page]).per(20)
- @issues = @issues.includes(:author, :project)
+ @issues = IssuesFinder.new.execute(current_user, params)
+ @issues = @issues.page(params[:page]).per(20)
+ @issues = @issues.preload(:author, :project)
respond_to do |format|
format.html
@@ -72,6 +74,12 @@ class DashboardController < ApplicationController
protected
def load_projects
- @projects = current_user.authorized_projects.sorted_by_activity
+ @projects = current_user.authorized_projects.sorted_by_activity.non_archived
+ end
+
+ def default_filter
+ params[:scope] = 'assigned-to-me' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+ params[:authorized_only] = true
end
end
diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb
deleted file mode 100644
index a0c8a000fc..0000000000
--- a/app/controllers/errors_controller.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class ErrorsController < ApplicationController
-end
diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb
new file mode 100644
index 0000000000..38071410f4
--- /dev/null
+++ b/app/controllers/groups/avatars_controller.rb
@@ -0,0 +1,12 @@
+class Groups::AvatarsController < ApplicationController
+ layout "profile"
+
+ def destroy
+ @group = Group.find_by(path: params[:group_id])
+ @group.remove_avatar!
+
+ @group.save
+
+ redirect_to edit_group_path(@group)
+ end
+end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index bb46af14d5..a3019b3ac7 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,4 +1,5 @@
class GroupsController < ApplicationController
+ skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests]
respond_to :html
before_filter :group, except: [:new, :create]
@@ -10,6 +11,8 @@ class GroupsController < ApplicationController
# Load group projects
before_filter :projects, except: [:new, :create]
+ before_filter :default_filter, only: [:issues, :merge_requests]
+
layout :determine_layout
before_filter :set_title, only: [:new, :create]
@@ -34,28 +37,25 @@ class GroupsController < ApplicationController
@events = Event.in_projects(project_ids)
@events = event_filter.apply_filter(@events)
@events = @events.limit(20).offset(params[:offset] || 0)
- @last_push = current_user.recent_push
+ @last_push = current_user.recent_push if current_user
respond_to do |format|
format.html
- format.js
+ format.json { pager_json("events/_events", @events.count) }
format.atom { render layout: false }
end
end
- # Get authored or assigned open merge requests
def merge_requests
- @merge_requests = current_user.cared_merge_requests.of_group(@group)
- @merge_requests = FilterContext.new(@merge_requests, params).execute
- @merge_requests = @merge_requests.recent.page(params[:page]).per(20)
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
+ @merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = @merge_requests.preload(:author, :target_project)
end
- # Get only assigned issues
def issues
- @issues = current_user.assigned_issues.of_group(@group)
- @issues = FilterContext.new(@issues, params).execute
- @issues = @issues.recent.page(params[:page]).per(20)
- @issues = @issues.includes(:author, :project)
+ @issues = IssuesFinder.new.execute(current_user, params)
+ @issues = @issues.page(params[:page]).per(20)
+ @issues = @issues.preload(:author, :project)
respond_to do |format|
format.html
@@ -65,7 +65,14 @@ class GroupsController < ApplicationController
def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
- @members = group.users_groups.order('group_access DESC')
+ @members = group.users_groups
+
+ if params[:search].present?
+ users = group.users.search(params[:search])
+ @members = @members.where(user_id: users)
+ end
+
+ @members = @members.order('group_access DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new
end
@@ -89,21 +96,25 @@ class GroupsController < ApplicationController
protected
def group
- @group ||= Group.find_by_path(params[:id])
+ @group ||= Group.find_by(path: params[:id])
end
def projects
- @projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
+ @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
end
def project_ids
- projects.map(&:id)
+ projects.pluck(:id)
end
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (projects.present? or can?(current_user, :read_group, @group))
- return render_404
+ if current_user.nil?
+ return authenticate_user!
+ else
+ return render_404
+ end
end
end
@@ -126,8 +137,22 @@ class GroupsController < ApplicationController
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'navless'
- else
+ elsif current_user
'group'
+ else
+ 'public_group'
end
end
+
+ def default_filter
+ if params[:scope].blank?
+ if current_user
+ params[:scope] = 'assigned-to-me'
+ else
+ params[:scope] = 'all'
+ end
+ end
+ params[:state] = 'opened' if params[:state].blank?
+ params[:group_id] = @group.id
+ end
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
new file mode 100644
index 0000000000..988ede3007
--- /dev/null
+++ b/app/controllers/passwords_controller.rb
@@ -0,0 +1,18 @@
+class PasswordsController < Devise::PasswordsController
+
+ def create
+ email = resource_params[:email]
+ resource_found = resource_class.find_by_email(email)
+ if resource_found && resource_found.ldap_user?
+ flash[:alert] = "Cannot reset password for LDAP user."
+ respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return
+ end
+
+ self.resource = resource_class.send_reset_password_instructions(resource_params)
+ if successfully_sent?(resource)
+ respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
+ else
+ respond_with(resource)
+ end
+ end
+end
diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb
new file mode 100644
index 0000000000..57f3bbf062
--- /dev/null
+++ b/app/controllers/profiles/avatars_controller.rb
@@ -0,0 +1,13 @@
+class Profiles::AvatarsController < ApplicationController
+ layout "profile"
+
+ def destroy
+ @user = current_user
+ @user.remove_avatar!
+
+ @user.save
+ @user.reset_events_cache
+
+ redirect_to profile_path
+ end
+end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
new file mode 100644
index 0000000000..40c352dab0
--- /dev/null
+++ b/app/controllers/profiles/emails_controller.rb
@@ -0,0 +1,26 @@
+class Profiles::EmailsController < ApplicationController
+ layout "profile"
+
+ def index
+ @primary = current_user.email
+ @emails = current_user.emails
+ end
+
+ def create
+ @email = current_user.emails.new(params[:email])
+
+ flash[:alert] = @email.errors.full_messages.first unless @email.save
+
+ redirect_to profile_emails_url
+ end
+
+ def destroy
+ @email = current_user.emails.find(params[:id])
+ @email.destroy
+
+ respond_to do |format|
+ format.html { redirect_to profile_emails_url }
+ format.js { render nothing: true }
+ end
+ end
+end
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
index 378ff6bcf3..9a4d088651 100644
--- a/app/controllers/profiles/groups_controller.rb
+++ b/app/controllers/profiles/groups_controller.rb
@@ -7,18 +7,17 @@ class Profiles::GroupsController < ApplicationController
def leave
@users_group = group.users_groups.where(user_id: current_user.id).first
-
- if group.last_owner?(current_user)
- redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.")
- else
+ if can?(current_user, :destroy, @users_group)
@users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.")
+ else
+ return render_403
end
end
private
def group
- @group ||= Group.find_by_path(params[:id])
+ @group ||= Group.find_by(path: params[:id])
end
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index c36dae2abd..6713cd7c8c 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -1,8 +1,9 @@
class Profiles::KeysController < ApplicationController
layout "profile"
+ skip_before_filter :authenticate_user!, only: [:get_keys]
def index
- @keys = current_user.keys.order('id DESC').all
+ @keys = current_user.keys.order('id DESC')
end
def show
@@ -32,4 +33,24 @@ class Profiles::KeysController < ApplicationController
format.js { render nothing: true }
end
end
+
+ # Get all keys of a user(params[:username]) in a text format
+ # Helpful for sysadmins to put in respective servers
+ def get_keys
+ if params[:username].present?
+ begin
+ user = User.find_by_username(params[:username])
+ if user.present?
+ render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
+ else
+ render_404 and return
+ end
+ rescue => e
+ render text: e.message
+ end
+ else
+ render_404 and return
+ end
+ end
+
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 47cfc5e63f..9c9a129b26 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -3,6 +3,7 @@ class ProfilesController < ApplicationController
before_filter :user
before_filter :authorize_change_username!, only: :update_username
+ skip_before_filter :require_email, only: [:show, :update]
layout 'profile'
@@ -13,6 +14,8 @@ class ProfilesController < ApplicationController
end
def update
+ params[:user].delete(:email) if @user.ldap_user?
+
if @user.update_attributes(params[:user])
flash[:notice] = "Profile was successfully updated"
else
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 8fd4565f36..7e4580017d 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController
id = params[:project_id] || params[:id]
@project = Project.find_with_namespace(id)
- return if @project && @project.public
+ return if @project && @project.public?
end
super
@@ -23,4 +23,10 @@ class Projects::ApplicationController < ApplicationController
'public_projects'
end
end
+
+ def require_branch_head
+ unless @repository.branch_names.include?(@ref)
+ redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
+ end
+ end
end
diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb
new file mode 100644
index 0000000000..5e30593443
--- /dev/null
+++ b/app/controllers/projects/base_tree_controller.rb
@@ -0,0 +1,8 @@
+class Projects::BaseTreeController < Projects::ApplicationController
+ include ExtractsPath
+
+ before_filter :authorize_read_project!
+ before_filter :authorize_code_access!
+ before_filter :require_non_empty_project
+end
+
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index ba466251b2..a1a8bed09f 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -6,10 +6,32 @@ class Projects::BlobController < Projects::ApplicationController
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :authorize_push!, only: [:destroy]
+
+ before_filter :blob
def show
- @blob = @repository.blob_at(@commit.id, @path)
+ end
- not_found! unless @blob
+ def destroy
+ result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+ redirect_to project_tree_path(@project, @ref)
+ else
+ flash[:alert] = result[:error]
+ render :show
+ end
+ end
+
+ private
+
+ def blob
+ @blob ||= @repository.blob_at(@commit.id, @path)
+
+ return not_found! unless @blob
+
+ @blob
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index aa6914414c..6a6cbe4818 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -16,11 +16,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
- @repository.add_branch(params[:branch_name], params[:ref])
-
- if new_branch = @repository.find_branch(params[:branch_name])
- Event.create_ref_event(@project, current_user, new_branch, 'add')
- end
+ CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user)
redirect_to project_branches_path(@project)
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 242aa41182..c56df65dcb 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -6,34 +6,35 @@ class Projects::CommitController < Projects::ApplicationController
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :commit
def show
- result = CommitLoadContext.new(project, current_user, params).execute
+ return git_not_found! unless @commit
- @commit = result[:commit]
+ @line_notes = project.notes.for_commit_id(commit.id).inline
+ @branches = project.repository.branch_names_contains(commit.id)
- if @commit.nil?
- git_not_found!
- return
+ begin
+ @suppress_diff = true if commit.diff_suppress? && !params[:force_show_diff]
+ @force_suppress_diff = commit.diff_force_suppress?
+ rescue Grit::Git::GitTimeout
+ @suppress_diff = true
+ @status = :huge_commit
end
- @suppress_diff = result[:suppress_diff]
- @force_suppress_diff = result[:force_suppress_diff]
-
- @note = result[:note]
- @line_notes = result[:line_notes]
- @branches = result[:branches]
- @notes_count = result[:notes_count]
- @target_type = :commit
- @target_id = @commit.id
-
+ @note = project.build_commit_note(commit)
+ @notes_count = project.notes.for_commit_id(commit.id).count
+ @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
+ @noteable = @commit
@comments_allowed = @reply_allowed = true
- @comments_target = { noteable_type: 'Commit',
- commit_id: @commit.id }
+ @comments_target = {
+ noteable_type: 'Commit',
+ commit_id: @commit.id
+ }
respond_to do |format|
format.html do
- if result[:status] == :huge_commit
+ if @status == :huge_commit
render "huge_commit" and return
end
end
@@ -42,4 +43,8 @@ class Projects::CommitController < Projects::ApplicationController
format.patch { render text: @commit.to_patch }
end
end
+
+ def commit
+ @commit ||= project.repository.commit(params[:id])
+ end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index bdffc940ea..12856191c2 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -16,7 +16,7 @@ class Projects::CommitsController < Projects::ApplicationController
respond_to do |format|
format.html # index.html.erb
- format.js
+ format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false }
end
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index b7531e2cef..234b6058ff 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -8,13 +8,14 @@ class Projects::CompareController < Projects::ApplicationController
end
def show
- compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to])
+ compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE)
@commits = compare.commits
@commit = compare.commit
@diffs = compare.diffs
@refs_are_same = compare.same
@line_notes = []
+ @timeout = compare.timeout
diff_line_count = Commit::diff_line_count(@diffs)
@suppress_diff = Commit::diff_suppress?(@diffs, diff_line_count) && !params[:force_show_diff]
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 0750e0a146..6e1a76ff41 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -7,7 +7,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
- @enabled_keys = @project.deploy_keys.all
+ @enabled_keys = @project.deploy_keys
@available_keys = available_keys - @enabled_keys
end
diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
index 0e51ff59f3..be611892bb 100644
--- a/app/controllers/projects/edit_tree_controller.rb
+++ b/app/controllers/projects/edit_tree_controller.rb
@@ -1,53 +1,61 @@
-# Controller for edit a repository's file
-class Projects::EditTreeController < Projects::ApplicationController
- include ExtractsPath
-
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
- before_filter :edit_requirements, only: [:show, :update]
+class Projects::EditTreeController < Projects::BaseTreeController
+ before_filter :require_branch_head
+ before_filter :blob
+ before_filter :authorize_push!
+ before_filter :from_merge_request
+ before_filter :after_edit_path
def show
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
end
def update
- edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
- updated_successfully = edit_file_action.commit!(
- params[:content],
- params[:commit_message],
- params[:last_commit]
- )
+ result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute
- if updated_successfully
- redirect_to project_blob_path(@project, @id), notice: "Your changes have been successfully commited"
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+
+ if from_merge_request
+ from_merge_request.reload_code
+ end
+
+ redirect_to after_edit_path
else
- flash[:notice] = "Your changes could not be commited, because the file has been changed"
+ flash[:alert] = result[:error]
render :show
end
end
+ def preview
+ @content = params[:content]
+ #FIXME workaround https://github.com/gitlabhq/gitlabhq/issues/5936
+ @content += "\n" if @blob.data.end_with?("\n")
+
+ diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
+ include_diff_info: true)
+ @diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/))
+
+ render layout: false
+ end
+
private
- def edit_requirements
- @blob = @repository.blob_at(@commit.id, @path)
+ def blob
+ @blob ||= @repository.blob_at(@commit.id, @path)
+ end
- unless @blob
- redirect_to project_blob_path(@project, @id), notice: "You can only edit text files"
- end
+ def after_edit_path
+ @after_edit_path ||=
+ if from_merge_request
+ diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
+ "#file-path-#{hexdigest(@path)}"
+ else
+ project_blob_path(@project, @id)
+ end
+ end
- allowed = if project.protected_branch? @ref
- can?(current_user, :push_code_to_protected_branches, project)
- else
- can?(current_user, :push_code, project)
- end
-
- return access_denied! unless allowed
-
- unless @repository.branch_names.include?(@ref)
- redirect_to project_blob_path(@project, @id), notice: "You can only edit this file if you are on top of a branch"
- end
+ def from_merge_request
+ # If blob edit was initiated from merge request page
+ @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 1a94dbab5e..c43d26385f 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -7,7 +7,7 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings"
def index
- @hooks = @project.hooks.all
+ @hooks = @project.hooks
@hook = ProjectHook.new
end
@@ -18,21 +18,26 @@ class Projects::HooksController < Projects::ApplicationController
if @hook.valid?
redirect_to project_hooks_path(@project)
else
- @hooks = @project.hooks.all
+ @hooks = @project.hooks.select(&:persisted?)
render :index
end
end
def test
- TestHookContext.new(project, current_user, params).execute
+ TestHookService.new.execute(hook, current_user)
redirect_to :back
end
def destroy
- @hook = @project.hooks.find(params[:id])
- @hook.destroy
+ hook.destroy
redirect_to project_hooks_path(@project)
end
+
+ private
+
+ def hook
+ @hook ||= @project.hooks.find(params[:id])
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index e8f845b2d1..4e7a716bfe 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -11,7 +11,10 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update]
- respond_to :js, :html
+ # Allow issues bulk update
+ before_filter :authorize_admin_issues!, only: [:bulk_update]
+
+ respond_to :html
def index
terms = params['issue_search']
@@ -23,11 +26,18 @@ class Projects::IssuesController < Projects::ApplicationController
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
+ sort_param = params[:sort] || 'newest'
+ @sort = sort_param.humanize unless sort_param.empty?
+ @assignees = User.where(id: @project.issues.pluck(:assignee_id))
respond_to do |format|
- format.html # index.html.erb
- format.js
+ format.html
format.atom { render layout: false }
+ format.json do
+ render json: {
+ html: view_to_html_string("projects/issues/_issues")
+ }
+ end
end
end
@@ -42,19 +52,14 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@note = @project.notes.new(noteable: @issue)
- @target_type = :issue
- @target_id = @issue.id
+ @notes = @issue.notes.inc_author.fresh
+ @noteable = @issue
- respond_to do |format|
- format.html
- format.js
- end
+ respond_with(@issue)
end
def create
- @issue = @project.issues.new(params[:issue])
- @issue.author = current_user
- @issue.save
+ @issue = Issues::CreateService.new(project, current_user, params[:issue]).execute
respond_to do |format|
format.html do
@@ -69,7 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update
- @issue.update_attributes(params[:issue].merge(author_id_of_changes: current_user.id))
+ @issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue)
respond_to do |format|
format.js
@@ -84,7 +89,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def bulk_update
- result = Issues::BulkUpdateContext.new(project, current_user, params).execute
+ result = Issues::BulkUpdateService.new(project, current_user, params).execute
redirect_to :back, notice: "#{result[:count]} issues updated"
end
@@ -92,7 +97,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
@issue ||= begin
- @project.issues.find_by_iid!(params[:id])
+ @project.issues.find_by!(iid: params[:id])
rescue ActiveRecord::RecordNotFound
redirect_old
end
@@ -102,8 +107,8 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless can?(current_user, :modify_issue, @issue)
end
- def authorize_admin_issue!
- return render_404 unless can?(current_user, :admin_issue, @issue)
+ def authorize_admin_issues!
+ return render_404 unless can?(current_user, :admin_issue, @project)
end
def module_enabled
@@ -111,7 +116,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issues_filtered
- @issues = Issues::ListContext.new(project, current_user, params).execute
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+ @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
end
# Since iids are implemented only in 6.1
@@ -120,7 +127,7 @@ class Projects::IssuesController < Projects::ApplicationController
# To prevent 404 errors we provide a redirect to correct iids until 7.0 release
#
def redirect_old
- issue = @project.issues.find_by_id(params[:id])
+ issue = @project.issues.find_by(id: params[:id])
if issue
redirect_to project_issue_path(@project, issue)
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 65f9e2b9d5..b037cf5650 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -1,8 +1,7 @@
class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled
- # Allow read any issue
- before_filter :authorize_read_issue!
+ before_filter :authorize_labels!
respond_to :js, :html
@@ -13,12 +12,18 @@ class Projects::LabelsController < Projects::ApplicationController
def generate
Gitlab::IssuesLabels.generate(@project)
- redirect_to project_labels_path(@project)
+ if params[:redirect] == 'issues'
+ redirect_to project_issues_path(@project)
+ elsif params[:redirect] == 'merge_requests'
+ redirect_to project_merge_requests_path(@project)
+ end
end
protected
def module_enabled
- return render_404 unless @project.issues_enabled
+ unless @project.issues_enabled || @project.merge_requests_enabled
+ return render_404
+ end
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 0cc09caf1d..d8551db7b0 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,8 +2,8 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :module_enabled
- before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status]
- before_filter :closes_issues, only: [:edit, :update, :show, :commits, :diffs]
+ before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status]
+ before_filter :closes_issues, only: [:edit, :update, :show, :diffs]
before_filter :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs]
@@ -17,17 +17,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index
- @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
+ params[:sort] ||= 'newest'
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+
+ @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
+ @merge_requests = @merge_requests.page(params[:page]).per(20)
+
+ @sort = params[:sort].humanize
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
@assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
+ @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
end
def show
respond_to do |format|
format.html
- format.js
-
format.diff { render text: @merge_request.to_diff(current_user) }
format.patch { render text: @merge_request.to_patch(current_user) }
end
@@ -44,15 +50,39 @@ class Projects::MergeRequestsController < Projects::ApplicationController
diff_line_count = Commit::diff_line_count(@merge_request.diffs)
@suppress_diff = Commit::diff_suppress?(@merge_request.diffs, diff_line_count) && !params[:force_show_diff]
@force_suppress_diff = Commit::diff_force_suppress?(@merge_request.diffs, diff_line_count)
+
+ respond_to do |format|
+ format.html
+ format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
+ end
end
def new
@merge_request = MergeRequest.new(params[:merge_request])
@merge_request.source_project = @project unless @merge_request.source_project
- @merge_request.target_project = @project unless @merge_request.target_project
+ @merge_request.target_project ||= (@project.forked_from_project || @project)
@target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
+ @merge_request.target_branch ||= @merge_request.target_project.default_branch
@source_project = @merge_request.source_project
- @merge_request
+
+ if @merge_request.target_branch && @merge_request.source_branch
+ compare_action = Gitlab::Satellite::CompareAction.new(
+ current_user,
+ @merge_request.target_project,
+ @merge_request.target_branch,
+ @merge_request.source_project,
+ @merge_request.source_branch
+ )
+
+ @commits = compare_action.commits
+ @commits.map! { |commit| Commit.new(commit) }
+ @commit = @commits.first
+
+ @diffs = compare_action.diffs
+ @merge_request.title = @merge_request.source_branch.titleize.humanize
+ @target_project = @merge_request.target_project
+ @target_repo = @target_project.repository
+ end
end
def edit
@@ -62,12 +92,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def create
- @merge_request = MergeRequest.new(params[:merge_request])
- @merge_request.author = current_user
@target_branches ||= []
- if @merge_request.save
- @merge_request.reload_code
- redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
+ @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
+
+ if @merge_request.valid?
+ redirect_to project_merge_request_path(@merge_request.target_project, @merge_request), notice: 'Merge request was successfully created.'
else
@source_project = @merge_request.source_project
@target_project = @merge_request.target_project
@@ -76,10 +105,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def update
- if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
- @merge_request.reload_code
- @merge_request.mark_as_unchecked
- redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+ @merge_request = MergeRequests::UpdateService.new(project, current_user, params[:merge_request]).execute(@merge_request)
+
+ if @merge_request.valid?
+ respond_to do |format|
+ format.js
+ format.html do
+ redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+ end
+ end
else
render "edit"
end
@@ -90,16 +124,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.check_if_can_be_merged
end
render json: {merge_status: @merge_request.merge_status_name}
- rescue Gitlab::SatelliteNotExistError
- render json: {merge_status: :no_satellite}
end
def automerge
return access_denied! unless allowed_to_merge?
- if @merge_request.opened? && @merge_request.can_be_merged?
+ if @merge_request.open? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
- @merge_request.automerge!(current_user)
+ @merge_request.automerge!(current_user, params[:merge_commit_message])
@status = true
else
@status = false
@@ -121,10 +153,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
@target_branches
+
+ respond_to do |format|
+ format.js
+ end
end
def ci_status
- status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
+ status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = {status: status}
render json: response
@@ -133,11 +169,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
protected
def selected_target_project
- ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
+ if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
+ @project
+ else
+ @project.forked_project_link.forked_from_project
+ end
end
def merge_request
- @merge_request ||= @project.merge_requests.find_by_iid!(params[:id])
+ @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
def closes_issues
@@ -157,44 +197,59 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def validates_merge_request
+ # If source project was removed (Ex. mr from fork to origin)
+ return invalid_mr unless @merge_request.source_project
+
# Show git not found page
# if there is no saved commits between source & target branch
if @merge_request.commits.blank?
- # and if source target doesn't exist
- return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
+ # and if target branch doesn't exist
+ return invalid_mr unless @merge_request.target_branch_exists?
- # or if source branch doesn't exist
- return invalid_mr unless @merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch)
+ # or if source branch doesn't exist
+ return invalid_mr unless @merge_request.source_branch_exists?
end
end
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
+ @notes = @merge_request.mr_and_commit_notes.inc_author.fresh
+ @discussions = Note.discussions_from_notes(@notes)
+ @noteable = @merge_request
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
+ @merge_request_diff = @merge_request.merge_request_diff
@allowed_to_merge = allowed_to_merge?
- @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
-
- @target_type = :merge_request
- @target_id = @merge_request.id
+ @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
+ @allowed_to_remove_source_branch = allowed_to_remove_source_branch?
+ @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
end
def allowed_to_merge?
- action = if project.protected_branch?(@merge_request.target_branch)
- :push_code_to_protected_branches
- else
- :push_code
- end
-
- can?(current_user, action, @project)
+ allowed_to_push_code?(project, @merge_request.target_branch)
end
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
end
+
+ def allowed_to_remove_source_branch?
+ allowed_to_push_code?(@merge_request.source_project, @merge_request.source_branch) &&
+ !@merge_request.disallow_source_branch_removal?
+ end
+
+ def allowed_to_push_code?(project, branch)
+ action = if project.protected_branch?(branch)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+
+ can?(current_user, action, project)
+ end
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 3e88656cdf..05237bbd2d 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -34,16 +34,10 @@ class Projects::MilestonesController < Projects::ApplicationController
@issues = @milestone.issues
@users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests
-
- respond_to do |format|
- format.html
- format.js
- end
end
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)
@@ -53,7 +47,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def update
- @milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id))
+ @milestone.update_attributes(params[:milestone])
respond_to do |format|
format.js
@@ -81,7 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
def milestone
- @milestone ||= @project.milestones.find_by_iid!(params[:id])
+ @milestone ||= @project.milestones.find_by!(iid: params[:id])
end
def authorize_admin_milestone!
diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb
new file mode 100644
index 0000000000..3a51a78ef6
--- /dev/null
+++ b/app/controllers/projects/new_tree_controller.rb
@@ -0,0 +1,20 @@
+class Projects::NewTreeController < Projects::BaseTreeController
+ before_filter :require_branch_head
+ before_filter :authorize_push!
+
+ def show
+ end
+
+ def update
+ file_path = File.join(@path, File.basename(params[:file_name]))
+ result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+ redirect_to project_blob_path(@project, File.join(@ref, file_path))
+ else
+ flash[:alert] = result[:error]
+ render :show
+ end
+ end
+end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 8214163c31..b5b0446b43 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -2,62 +2,55 @@ class Projects::NotesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_note!
before_filter :authorize_write_note!, only: [:create]
-
- respond_to :js
+ before_filter :authorize_admin_note!, only: [:update, :destroy]
def index
- @notes = Notes::LoadContext.new(project, current_user, params).execute
- @target_type = params[:target_type].camelize
- @target_id = params[:target_id]
+ current_fetched_at = Time.now.to_i
+ @notes = NotesFinder.new.execute(project, current_user, params)
- if params[:target_type] == "merge_request"
- @discussions = discussions_from_notes
+ notes_json = { notes: [], last_fetched_at: current_fetched_at }
+
+ @notes.each do |note|
+ notes_json[:notes] << {
+ id: note.id,
+ html: note_to_html(note)
+ }
end
- respond_with(@notes)
+ render json: notes_json
end
def create
- @note = Notes::CreateContext.new(project, current_user, params).execute
- @target_type = params[:target_type].camelize
- @target_id = params[:target_id]
+ @note = Notes::CreateService.new(project, current_user, params).execute
respond_to do |format|
- format.html {redirect_to :back}
- format.js
+ format.json { render_note_json(@note) }
+ format.html { redirect_to :back }
+ end
+ end
+
+ def update
+ note.update_attributes(params[:note])
+ note.reset_events_cache
+
+ respond_to do |format|
+ format.json { render_note_json(note) }
+ format.html { redirect_to :back }
end
end
def destroy
- @note = @project.notes.find(params[:id])
- return access_denied! unless can?(current_user, :admin_note, @note)
- @note.destroy
+ note.destroy
+ note.reset_events_cache
respond_to do |format|
format.js { render nothing: true }
end
end
- def update
- @note = @project.notes.find(params[:id])
- return access_denied! unless can?(current_user, :admin_note, @note)
-
- @note.update_attributes(params[:note])
-
- respond_to do |format|
- format.js do
- render js: { success: @note.valid?, id: @note.id, note: view_context.markdown(@note.note) }.to_json
- end
- format.html do
- redirect_to :back
- end
- end
- end
-
def delete_attachment
- @note = @project.notes.find(params[:id])
- @note.remove_attachment!
- @note.update_attribute(:attachment, nil)
+ note.remove_attachment!
+ note.update_attribute(:attachment, nil)
respond_to do |format|
format.js { render nothing: true }
@@ -68,35 +61,40 @@ class Projects::NotesController < Projects::ApplicationController
render text: view_context.markdown(params[:note])
end
- protected
+ private
- def discussion_notes_for(note)
- @notes.select do |other_note|
- note.discussion_id == other_note.discussion_id
- end
+ def note
+ @note ||= @project.notes.find(params[:id])
end
- def discussions_from_notes
- discussion_ids = []
- discussions = []
-
- @notes.each do |note|
- next if discussion_ids.include?(note.discussion_id)
-
- # don't group notes for the main target
- if note_for_main_target?(note)
- discussions << [note]
- else
- discussions << discussion_notes_for(note)
- discussion_ids << note.discussion_id
- end
- end
-
- discussions
+ def note_to_html(note)
+ render_to_string(
+ "projects/notes/_note",
+ layout: false,
+ formats: [:html],
+ locals: { note: note }
+ )
end
- # Helps to distinguish e.g. commit notes in mr notes list
- def note_for_main_target?(note)
- (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
+ def note_to_discussion_html(note)
+ render_to_string(
+ "projects/notes/_diff_notes_with_reply",
+ layout: false,
+ formats: [:html],
+ locals: { notes: [note] }
+ )
+ end
+
+ def render_note_json(note)
+ render json: {
+ id: note.id,
+ discussion_id: note.discussion_id,
+ html: note_to_html(note),
+ discussion_html: note_to_discussion_html(note)
+ }
+ end
+
+ def authorize_admin_note!
+ return access_denied! unless can?(current_user, :admin_note, note)
end
end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 81531bb0ac..cfc1bd99a2 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -6,7 +6,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
before_filter :authorize_admin_project!, only: [:destroy, :create]
def index
- @branches = @project.protected_branches.all
+ @branches = @project.protected_branches.to_a
@protected_branch = @project.protected_branches.new
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 18ace028b0..a6b7ae3f12 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -11,11 +11,7 @@ class Projects::RawController < Projects::ApplicationController
@blob = @repository.blob_at(@commit.id, @path)
if @blob
- type = if @blob.mime_type =~ /html|javascript/
- 'text/plain; charset=utf-8'
- else
- @blob.mime_type
- end
+ type = get_blob_type
headers['X-Content-Type-Options'] = 'nosniff'
@@ -29,5 +25,17 @@ class Projects::RawController < Projects::ApplicationController
not_found!
end
end
+
+ private
+
+ def get_blob_type
+ if @blob.mime_type =~ /html|javascript/
+ 'text/plain; charset=utf-8'
+ elsif @blob.name =~ /(?:msi|exe|rar|r0\d|7z|7zip|zip)$/
+ 'application/octet-stream'
+ else
+ @blob.mime_type
+ end
+ end
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 16621c0371..7b8026cff9 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -34,7 +34,7 @@ class Projects::RefsController < Projects::ApplicationController
contents = tree.entries
@logs = contents.map do |content|
file = params[:path] ? File.join(params[:path], content.name) : content.name
- last_commit = @repo.commits(@commit.id, file, 1).last
+ last_commit = @repo.last_commit_for_path(@commit.id, file)
{
file_name: content.name,
commit: last_commit
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 20e2a9311e..28fed8b0e3 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -14,9 +14,11 @@ class Projects::RepositoriesController < Projects::ApplicationController
render_404 and return
end
- storage_path = Rails.root.join("tmp", "repositories")
+ storage_path = Gitlab.config.gitlab.repository_downloads_path
- file_path = @repository.archive_repo(params[:ref], storage_path)
+ @repository.clean_old_archives
+
+ file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase)
if file_path
# Send file to user
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index dd0c1a5708..f93f2d5f9b 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -48,8 +48,8 @@ class Projects::SnippetsController < Projects::ApplicationController
def show
@note = @project.notes.new(noteable: @snippet)
- @target_type = :snippet
- @target_id = @snippet.id
+ @notes = @snippet.notes.fresh
+ @noteable = @snippet
end
def destroy
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 9dbb0d8188..0b99165bf6 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -8,7 +8,8 @@ class Projects::TagsController < Projects::ApplicationController
before_filter :authorize_admin_project!, only: [:destroy]
def index
- @tags = Kaminari.paginate_array(@repository.tags).page(params[:page]).per(30)
+ sorted = VersionSorter.rsort(@repository.tag_names)
+ @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(30)
end
def create
diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb
index b4b318fa59..44068878cd 100644
--- a/app/controllers/projects/team_members_controller.rb
+++ b/app/controllers/projects/team_members_controller.rb
@@ -1,6 +1,6 @@
class Projects::TeamMembersController < Projects::ApplicationController
# Authorize
- before_filter :authorize_admin_project!
+ before_filter :authorize_admin_project!, except: :leave
layout "project_settings"
@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
- @user_project_relation = project.users_projects.find_by_user_id(member)
+ @user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation.update_attributes(params[:team_member])
unless @user_project_relation.valid?
@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
- @user_project_relation = project.users_projects.find_by_user_id(member)
+ @user_project_relation = project.users_projects.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
@@ -45,6 +45,15 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
end
+ def leave
+ project.users_projects.find_by(user_id: current_user).destroy
+
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js { render nothing: true }
+ end
+ end
+
def apply_import
giver = Project.find(params[:source_project_id])
status = @project.team.import(giver)
@@ -56,6 +65,6 @@ class Projects::TeamMembersController < Projects::ApplicationController
protected
def member
- @member ||= User.find_by_username(params[:id])
+ @member ||= User.find_by(username: params[:id])
end
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 1150efbea9..30c94ec6da 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,12 +1,5 @@
# Controller for viewing a repository's file structure
-class Projects::TreeController < Projects::ApplicationController
- include ExtractsPath
-
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-
+class Projects::TreeController < Projects::BaseTreeController
def show
return not_found! if tree.entries.empty?
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 797f3d3dd4..496064c9a6 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -1,62 +1,80 @@
+require 'project_wiki'
+
class Projects::WikisController < Projects::ApplicationController
before_filter :authorize_read_wiki!
before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
before_filter :authorize_admin_wiki!, only: :destroy
- before_filter :load_gollum_wiki
+ before_filter :load_project_wiki
def pages
- @wiki_pages = @gollum_wiki.pages
+ @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(30)
end
def show
- @wiki = @gollum_wiki.find_page(params[:id], params[:version_id])
+ @page = @project_wiki.find_page(params[:id], params[:version_id])
+ gollum_wiki = @project_wiki.wiki
+ file = gollum_wiki.file(params[:id], gollum_wiki.ref, true)
- if @wiki
+ if @page
render 'show'
+ elsif file
+ if file.on_disk?
+ send_file file.on_disk_path, disposition: 'inline'
+ else
+ send_data(
+ file.raw_data,
+ type: file.mime_type,
+ disposition: 'inline',
+ filename: file.name
+ )
+ end
else
return render('empty') unless can?(current_user, :write_wiki, @project)
- @wiki = WikiPage.new(@gollum_wiki)
- @wiki.title = params[:id]
+ @page = WikiPage.new(@project_wiki)
+ @page.title = params[:id]
render 'edit'
end
end
def edit
- @wiki = @gollum_wiki.find_page(params[:id])
+ @page = @project_wiki.find_page(params[:id])
end
def update
- @wiki = @gollum_wiki.find_page(params[:id])
+ @page = @project_wiki.find_page(params[:id])
return render('empty') unless can?(current_user, :write_wiki, @project)
- if @wiki.update(content, format, message)
- redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.'
+ if @page.update(content, format, message)
+ redirect_to [@project, @page], notice: 'Wiki was successfully updated.'
else
render 'edit'
end
end
def create
- @wiki = WikiPage.new(@gollum_wiki)
+ @page = WikiPage.new(@project_wiki)
- if @wiki.create(wiki_params)
- redirect_to project_wiki_path(@project, @wiki), notice: 'Wiki was successfully updated.'
+ if @page.create(wiki_params)
+ redirect_to project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.'
else
render action: "edit"
end
end
def history
- @wiki = @gollum_wiki.find_page(params[:id])
+ @page = @project_wiki.find_page(params[:id])
- redirect_to(project_wiki_path(@project, :home), notice: "Page not found") unless @wiki
+ unless @page
+ redirect_to(project_wiki_path(@project, :home), notice: "Page not found")
+ end
end
def destroy
- @wiki = @gollum_wiki.find_page(params[:id])
- @wiki.delete if @wiki
+ @page = @project_wiki.find_page(params[:id])
+ @page.delete if @page
+
redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted"
end
@@ -65,12 +83,12 @@ class Projects::WikisController < Projects::ApplicationController
private
- def load_gollum_wiki
- @gollum_wiki = GollumWiki.new(@project, current_user)
+ def load_project_wiki
+ @project_wiki = ProjectWiki.new(@project, current_user)
# Call #wiki to make sure the Wiki Repo is initialized
- @gollum_wiki.wiki
- rescue GollumWiki::CouldNotCreateWikiError => ex
+ @project_wiki.wiki
+ rescue ProjectWiki::CouldNotCreateWikiError => ex
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to @project
return false
@@ -91,5 +109,4 @@ class Projects::WikisController < Projects::ApplicationController
def message
params[:wiki][:message]
end
-
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 7264128691..ebb8a90c63 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
- before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer]
+ before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork]
@@ -20,23 +20,16 @@ class ProjectsController < ApplicationController
end
def create
- @project = ::Projects::CreateContext.new(current_user, params[:project]).execute
+ @project = ::Projects::CreateService.new(current_user, params[:project]).execute
+ flash[:notice] = 'Project was successfully created.' if @project.saved?
respond_to do |format|
- flash[:notice] = 'Project was successfully created.' if @project.saved?
- format.html do
- if @project.saved?
- redirect_to @project
- else
- render "new"
- end
- end
format.js
end
end
def update
- status = ::Projects::UpdateContext.new(@project, current_user, params).execute
+ status = ::Projects::UpdateService.new(@project, current_user, params).execute
respond_to do |format|
if status
@@ -51,36 +44,57 @@ class ProjectsController < ApplicationController
end
def transfer
- ::Projects::TransferContext.new(project, current_user, params).execute
+ ::Projects::TransferService.new(project, current_user, params).execute
end
def show
- return authenticate_user! unless @project.public || current_user
+ if @project.import_in_progress?
+ redirect_to import_project_path(@project)
+ return
+ end
+
+ return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i
@events = @project.events.recent
@events = event_filter.apply_filter(@events)
@events = @events.limit(limit).offset(params[:offset] || 0)
- # Ensure project default branch is set if it possible
- # Normally it defined on push or during creation
- @project.discover_default_branch
-
respond_to do |format|
format.html do
if @project.empty_repo?
render "projects/empty", layout: user_layout
else
- if current_user
- @last_push = current_user.recent_push(@project.id)
- end
+ @last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout
end
end
- format.js
+ format.json { pager_json("events/_events", @events.count) }
end
end
+ def import
+ if project.import_finished?
+ redirect_to @project
+ return
+ end
+ end
+
+ def retry_import
+ unless @project.import_failed?
+ redirect_to import_project_path(@project)
+ end
+
+ @project.import_url = params[:project][:import_url]
+
+ if @project.save
+ @project.reload
+ @project.import_retry
+ end
+
+ redirect_to import_project_path(@project)
+ end
+
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
@@ -93,7 +107,7 @@ class ProjectsController < ApplicationController
end
def fork
- @forked_project = ::Projects::ForkContext.new(project, current_user).execute
+ @forked_project = ::Projects::ForkService.new(project, current_user).execute
respond_to do |format|
format.html do
@@ -109,10 +123,20 @@ class ProjectsController < ApplicationController
end
def autocomplete_sources
+ note_type = params['type']
+ note_id = params['type_id']
+ participating = if note_type && note_id
+ participants_in(note_type, note_id)
+ else
+ []
+ end
+ team_members = sorted(@project.team.members)
+ participants = team_members + participating
@suggestions = {
- emojis: Emoji.names,
+ emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
issues: @project.issues.select([:iid, :title, :description]),
- members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ mergerequests: @project.merge_requests.select([:iid, :title, :description]),
+ members: participants.uniq
}
respond_to do |format|
@@ -120,6 +144,24 @@ class ProjectsController < ApplicationController
end
end
+ def archive
+ return access_denied! unless can?(current_user, :archive_project, project)
+ project.archive!
+
+ respond_to do |format|
+ format.html { redirect_to @project }
+ end
+ end
+
+ def unarchive
+ return access_denied! unless can?(current_user, :archive_project, project)
+ project.unarchive!
+
+ respond_to do |format|
+ format.html { redirect_to @project }
+ end
+ end
+
private
def set_title
@@ -129,4 +171,25 @@ class ProjectsController < ApplicationController
def user_layout
current_user ? "projects" : "public_projects"
end
+
+ def participants_in(type, id)
+ users = case type
+ when "Issue"
+ issue = @project.issues.find_by_iid(id)
+ issue ? issue.participants : []
+ when "MergeRequest"
+ merge_request = @project.merge_requests.find_by_iid(id)
+ merge_request ? merge_request.participants : []
+ when "Commit"
+ author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
+ User.where(id: author_ids)
+ else
+ []
+ end
+ sorted(users)
+ end
+
+ def sorted(users)
+ users.uniq.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ end
end
diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb
index 87e903a1d2..d6238f7954 100644
--- a/app/controllers/public/projects_controller.rb
+++ b/app/controllers/public/projects_controller.rb
@@ -6,8 +6,9 @@ class Public::ProjectsController < ApplicationController
layout 'public'
def index
- @projects = Project.public_only
+ @projects = Project.publicish(current_user)
@projects = @projects.search(params[:search]) if params[:search].present?
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
+ @projects = @projects.sort(@sort = params[:sort])
+ @projects = @projects.includes(:namespace).page(params[:page]).per(20)
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 6eaa976f75..8df84e9884 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,27 +1,24 @@
class SearchController < ApplicationController
+ include SearchHelper
+
def show
- project_id = params[:project_id]
- group_id = params[:group_id]
+ @project = Project.find_by(id: params[:project_id]) if params[:project_id].present?
+ @group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
- project_ids = current_user.authorized_projects.map(&:id)
+ if @project
+ return access_denied! unless can?(current_user, :download_code, @project)
- if group_id.present?
- @group = Group.find(group_id)
- group_project_ids = @group.projects.map(&:id)
- project_ids.select! { |id| group_project_ids.include?(id)}
- elsif project_id.present?
- @project = Project.find(params[:project_id])
- project_ids.select! { |id| id == project_id.to_i}
+ @search_results = Search::ProjectService.new(@project, current_user, params).execute
+ else
+ @search_results = Search::GlobalService.new(current_user, params).execute
end
+ end
- result = SearchContext.new(project_ids, params).execute
+ def autocomplete
+ term = params[:term]
+ @project = Project.find(params[:project_id]) if params[:project_id].present?
+ @ref = params[:project_ref] if params[:project_ref].present?
- @projects = result[:projects]
- @merge_requests = result[:merge_requests]
- @issues = result[:issues]
- @notes = result[:notes]
- @wiki_pages = result[:wiki_pages]
- @blobs = Kaminari.paginate_array(result[:blobs]).page(params[:page]).per(20)
- @total_results = @projects.count + @merge_requests.count + @issues.count + @wiki_pages.count + @blobs.total_count
+ render json: search_autocomplete_opts(term).to_json
end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index b91f68aab5..0dd941a48e 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -18,7 +18,10 @@ class SnippetsController < ApplicationController
end
def user_index
- @user = User.find_by_username(params[:username])
+ @user = User.find_by(username: params[:username])
+
+ render_404 and return unless @user
+
@snippets = @user.snippets.fresh.non_expired
if @user == current_user
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 4947c33f95..9461174b95 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,11 +1,23 @@
class UsersController < ApplicationController
- layout 'navless'
+ skip_before_filter :authenticate_user!, only: [:show]
+ layout :determine_layout
def show
@user = User.find_by_username!(params[:username])
- @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id))
- @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
-
+ @projects = @user.authorized_projects.accessible_to(current_user)
+ if !current_user && @projects.empty?
+ return authenticate_user!
+ end
+ @groups = @user.groups.accessible_to(current_user)
+ @events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20)
@title = @user.name
end
+
+ def determine_layout
+ if current_user
+ 'navless'
+ else
+ 'public_users'
+ end
+ end
end
diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb
index 749da1e141..b9bdc18952 100644
--- a/app/controllers/users_groups_controller.rb
+++ b/app/controllers/users_groups_controller.rb
@@ -19,18 +19,21 @@ class UsersGroupsController < ApplicationController
def destroy
@users_group = @group.users_groups.find(params[:id])
- @users_group.destroy
-
- respond_to do |format|
- format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
- format.js { render nothing: true }
+ if can?(current_user, :destroy, @users_group) # May fail if last owner.
+ @users_group.destroy
+ respond_to do |format|
+ format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
+ format.js { render nothing: true }
+ end
+ else
+ return render_403
end
end
protected
def group
- @group ||= Group.find_by_path(params[:group_id])
+ @group ||= Group.find_by(path: params[:group_id])
end
def authorize_admin_group!
diff --git a/app/finders/README.md b/app/finders/README.md
new file mode 100644
index 0000000000..47823c51ef
--- /dev/null
+++ b/app/finders/README.md
@@ -0,0 +1,22 @@
+# Finders
+
+This type of classes responsible for collectiong items based on different conditions.
+To prevent lookup methods in models like this:
+
+```ruby
+class Project
+ def issues_for_user_filtered_by(user, filter)
+ # A lot of logic not related to project model itself
+ end
+end
+
+issues = project.issues_for_user_filtered_by(user, params)
+```
+
+Better use this:
+
+```ruby
+issues = IssuesFinder.new.execute(project, user, filter)
+```
+
+It will help keep models thiner
diff --git a/app/finders/base_finder.rb b/app/finders/base_finder.rb
new file mode 100644
index 0000000000..7fc5840561
--- /dev/null
+++ b/app/finders/base_finder.rb
@@ -0,0 +1,137 @@
+# BaseFinder
+#
+# Used to filter Issues and MergeRequests collections by set of params
+#
+# Arguments:
+# klass - actual class like Issue or MergeRequest
+# current_user - which user use
+# params:
+# scope: 'created-by-me' or 'assigned-to-me' or 'all'
+# state: 'open' or 'closed' or 'all'
+# group_id: integer
+# project_id: integer
+# milestone_id: integer
+# assignee_id: integer
+# search: string
+# label_name: string
+# sort: string
+#
+class BaseFinder
+ attr_accessor :current_user, :params
+
+ def execute(current_user, params)
+ @current_user = current_user
+ @params = params
+
+ items = init_collection
+ items = by_scope(items)
+ items = by_state(items)
+ items = by_group(items)
+ items = by_project(items)
+ items = by_search(items)
+ items = by_milestone(items)
+ items = by_assignee(items)
+ items = by_label(items)
+ items = sort(items)
+ end
+
+ private
+
+ def init_collection
+ table_name = klass.table_name
+
+ if project
+ if project.public? || (current_user && current_user.can?(:read_project, project))
+ project.send(table_name)
+ else
+ []
+ end
+ elsif current_user && params[:authorized_only].presence
+ klass.of_projects(current_user.authorized_projects).references(:project)
+ else
+ klass.of_projects(Project.accessible_to(current_user)).references(:project)
+ end
+ end
+
+ def by_scope(items)
+ case params[:scope]
+ when 'created-by-me', 'authored' then
+ items.where(author_id: current_user.id)
+ when 'all' then
+ items
+ when 'assigned-to-me' then
+ items.where(assignee_id: current_user.id)
+ else
+ raise 'You must specify default scope'
+ end
+ end
+
+ def by_state(items)
+ case params[:state]
+ when 'closed'
+ items.closed
+ when 'all'
+ items
+ when 'opened'
+ items.opened
+ else
+ raise 'You must specify default state'
+ end
+ end
+
+ def by_group(items)
+ if params[:group_id].present?
+ items = items.of_group(Group.find(params[:group_id]))
+ end
+
+ items
+ end
+
+ def by_project(items)
+ if params[:project_id].present?
+ items = items.of_projects(params[:project_id])
+ end
+
+ items
+ end
+
+ def by_search(items)
+ if params[:search].present?
+ items = items.search(params[:search])
+ end
+
+ items
+ end
+
+ def sort(items)
+ items.sort(params[:sort])
+ end
+
+ def by_milestone(items)
+ if params[:milestone_id].present?
+ items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id]))
+ end
+
+ items
+ end
+
+ def by_assignee(items)
+ if params[:assignee_id].present?
+ items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id]))
+ end
+
+ items
+ end
+
+ def by_label(items)
+ if params[:label_name].present?
+ items = items.tagged_with(params[:label_name])
+ end
+
+ items
+ end
+
+ def project
+ Project.where(id: params[:project_id]).first if params[:project_id].present?
+ end
+end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
new file mode 100644
index 0000000000..8e0c606249
--- /dev/null
+++ b/app/finders/issues_finder.rb
@@ -0,0 +1,22 @@
+# Finders::Issues class
+#
+# Used to filter Issues collections by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# scope: 'created-by-me' or 'assigned-to-me' or 'all'
+# state: 'open' or 'closed' or 'all'
+# group_id: integer
+# project_id: integer
+# milestone_id: integer
+# assignee_id: integer
+# search: string
+# label_name: string
+# sort: string
+#
+class IssuesFinder < BaseFinder
+ def klass
+ Issue
+ end
+end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
new file mode 100644
index 0000000000..3727149c8f
--- /dev/null
+++ b/app/finders/merge_requests_finder.rb
@@ -0,0 +1,22 @@
+# Finders::MergeRequest class
+#
+# Used to filter MergeRequests collections by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# scope: 'created-by-me' or 'assigned-to-me' or 'all'
+# state: 'open' or 'closed' or 'all'
+# group_id: integer
+# project_id: integer
+# milestone_id: integer
+# assignee_id: integer
+# search: string
+# label_name: string
+# sort: string
+#
+class MergeRequestsFinder < BaseFinder
+ def klass
+ MergeRequest
+ end
+end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
new file mode 100644
index 0000000000..ea055694cd
--- /dev/null
+++ b/app/finders/notes_finder.rb
@@ -0,0 +1,26 @@
+class NotesFinder
+ FETCH_OVERLAP = 5.seconds
+
+ def execute(project, current_user, params)
+ target_type = params[:target_type]
+ target_id = params[:target_id]
+ # Default to 0 to remain compatible with old clients
+ last_fetched_at = Time.at(params.fetch(:last_fetched_at, 0).to_i)
+
+ notes = case target_type
+ when "commit"
+ project.notes.for_commit_id(target_id).not_inline.fresh
+ when "issue"
+ project.issues.find(target_id).notes.inc_author.fresh
+ when "merge_request"
+ project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
+ when "snippet"
+ project.snippets.find(target_id).notes.fresh
+ else
+ raise 'invalid target_type'
+ end
+
+ # Use overlapping intervals to avoid worrying about race conditions
+ notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP)
+ end
+end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
new file mode 100644
index 0000000000..bfaba75878
--- /dev/null
+++ b/app/finders/projects_finder.rb
@@ -0,0 +1,63 @@
+class ProjectsFinder
+ def execute(current_user, options)
+ group = options[:group]
+
+ if group
+ group_projects(current_user, group)
+ else
+ all_projects(current_user)
+ end
+ end
+
+ private
+
+ def group_projects(current_user, group)
+ if current_user
+ if group.users.include?(current_user)
+ # User is group member
+ #
+ # Return ALL group projects
+ group.projects
+ else
+ projects_members = UsersProject.where(
+ project_id: group.projects,
+ user_id: current_user
+ )
+
+ if projects_members.any?
+ # User is a project member
+ #
+ # Return only:
+ # public projects
+ # internal projects
+ # joined projects
+ #
+ group.projects.where(
+ "projects.id IN (?) OR projects.visibility_level IN (?)",
+ projects_members.pluck(:project_id),
+ Project.public_and_internal_levels
+ )
+ else
+ # User has no access to group or group projects
+ #
+ # Return only:
+ # public projects
+ # internal projects
+ #
+ group.projects.public_and_internal_only
+ end
+ end
+ else
+ # Not authenticated
+ #
+ # Return only:
+ # public projects
+ group.projects.public_only
+ end
+ end
+
+ def all_projects
+ # TODO: implement
+ raise 'Not implemented yet'
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e2d9790141..5f07cdf448 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -49,8 +49,17 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
+ def group_icon(group_path)
+ group = Group.find_by(path: group_path)
+ if group && group.avatar.present?
+ group.avatar.url
+ else
+ image_path('no_group_avatar.png')
+ end
+ end
+
def avatar_icon(user_email = '', size = nil)
- user = User.find_by_email(user_email)
+ user = User.find_by(email: user_email)
if user && user.avatar.present?
user.avatar.url
else
@@ -62,17 +71,17 @@ module ApplicationHelper
size = 40 if size.nil? || size <= 0
if !Gitlab.config.gravatar.enabled || user_email.blank?
- 'no_avatar.png'
+ '/assets/no_avatar.png'
else
gravatar_url = request.ssl? || gitlab_config.https ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
user_email.strip!
- sprintf gravatar_url, hash: Digest::MD5.hexdigest(user_email.downcase), size: size
+ sprintf gravatar_url, hash: Digest::MD5.hexdigest(user_email.downcase), size: size, email: user_email
end
end
def last_commit(project)
if project.repo_exists?
- time_ago_in_words(project.repository.commit.committed_date) + " ago"
+ time_ago_with_tooltip(project.repository.commit.committed_date)
else
"Never"
end
@@ -80,16 +89,15 @@ module ApplicationHelper
"Never"
end
- def grouped_options_refs(destination = :tree)
+ def grouped_options_refs
repository = @project.repository
options = [
- ["Branch", repository.branch_names ],
- [ "Tag", repository.tag_names ]
+ ["Branches", repository.branch_names],
+ ["Tags", VersionSorter.rsort(repository.tag_names)]
]
- # If reference is commit id -
- # we should add it to branch/tag selectbox
+ # If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]]
@@ -126,6 +134,9 @@ module ApplicationHelper
# Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
+ # Skip if user removed branch right after that
+ return false unless project.repository.branch_names.include?(event.branch_name)
+
true
end
@@ -133,18 +144,9 @@ module ApplicationHelper
Digest::SHA1.hexdigest string
end
- def project_last_activity project
- if project.last_activity_at
- time_ago_in_words(project.last_activity_at) + " ago"
- else
- "Never"
- end
- end
-
def authbutton(provider, size = 64)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
- image_tag("authbuttons/#{file_name}",
- alt: "Sign in with #{provider.to_s.titleize}")
+ image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
end
def simple_sanitize(str)
@@ -158,15 +160,6 @@ module ApplicationHelper
alias_method :url_to_image, :image_url
- def users_select_tag(id, opts = {})
- css_class = "ajax-users-select "
- css_class << "multiselect " if opts[:multiple]
- css_class << (opts[:class] || '')
- value = opts[:selected] || ''
-
- hidden_field_tag(id, value, class: css_class)
- end
-
def body_data_page
path = controller.controller_path.split('/')
namespace = path.first if path.second
@@ -184,14 +177,6 @@ module ApplicationHelper
Gitlab.config.extra
end
- def public_icon
- content_tag :i, nil, class: 'icon-globe cblue'
- end
-
- def private_icon
- content_tag :i, nil, class: 'icon-lock cgreen'
- end
-
def search_placeholder
if @project && @project.persisted?
"Search in this project"
@@ -208,4 +193,42 @@ module ApplicationHelper
line += "..." if lines.size > 1
line
end
+
+ def broadcast_message
+ BroadcastMessage.current
+ end
+
+ def highlight_js(&block)
+ string = capture(&block)
+
+ content_tag :div, class: "highlighted-data #{user_color_scheme_class}" do
+ content_tag :div, class: 'highlight' do
+ content_tag :pre do
+ content_tag :code do
+ string.html_safe
+ end
+ end
+ end
+ end
+ end
+
+ def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
+ capture_haml do
+ haml_tag :time, date.to_s,
+ class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"),
+ data: { toggle: 'tooltip', placement: placement }
+
+ haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
+ end.html_safe
+ end
+
+ def render_markup(file_name, file_content)
+ GitHub::Markup.render(file_name, file_content).html_safe
+ end
+
+ def spinner(text = nil)
+ content_tag :div, class: 'loading hide' do
+ content_tag(:i, nil, class: 'icon-spinner icon-spin') + text
+ end
+ end
end
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
new file mode 100644
index 0000000000..29ff47663d
--- /dev/null
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -0,0 +1,9 @@
+module BroadcastMessagesHelper
+ def broadcast_styling(broadcast_message)
+ if(broadcast_message.color || broadcast_message.font)
+ "background-color:#{broadcast_message.color};color:#{broadcast_message.font}"
+ else
+ ""
+ end
+ end
+end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index c340eb30be..c4abdbdabc 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -16,9 +16,10 @@ module CommitsHelper
end
def each_diff_line(diff, index)
- Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|
- yield(full_line, type, line_code, line_new, line_old)
- end
+ Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
+ .each do |full_line, type, line_code, line_new, line_old|
+ yield(full_line, type, line_code, line_new, line_old)
+ end
end
def each_diff_line_near(diff, index, expected_line_code)
@@ -105,6 +106,82 @@ module CommitsHelper
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
end
+ def parallel_diff_lines(project, commit, diff, file)
+ old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id
+ deleted_lines = {}
+ added_lines = {}
+ each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old|
+ if type == "old"
+ deleted_lines[line_old] = { line_code: line_code, type: type, line: line }
+ elsif type == "new"
+ added_lines[line_new] = { line_code: line_code, type: type, line: line }
+ end
+ end
+ max_length = old_file ? [old_file.loc, file.loc].max : file.loc
+
+ offset1 = 0
+ offset2 = 0
+ old_lines = []
+ new_lines = []
+
+ max_length.times do |line_index|
+ line_index1 = line_index - offset1
+ line_index2 = line_index - offset2
+ deleted_line = deleted_lines[line_index1 + 1]
+ added_line = added_lines[line_index2 + 1]
+ old_line = old_file.lines[line_index1] if old_file
+ new_line = file.lines[line_index2]
+
+ if deleted_line && added_line
+ elsif deleted_line
+ new_line = nil
+ offset2 += 1
+ elsif added_line
+ old_line = nil
+ offset1 += 1
+ end
+
+ old_lines[line_index] = DiffLine.new
+ new_lines[line_index] = DiffLine.new
+
+ # old
+ if line_index == 0 && diff.new_file
+ old_lines[line_index].type = :file_created
+ old_lines[line_index].content = 'File was created'
+ elsif deleted_line
+ old_lines[line_index].type = :deleted
+ old_lines[line_index].content = old_line
+ old_lines[line_index].num = line_index1 + 1
+ old_lines[line_index].code = deleted_line[:line_code]
+ elsif old_line
+ old_lines[line_index].type = :no_change
+ old_lines[line_index].content = old_line
+ old_lines[line_index].num = line_index1 + 1
+ else
+ old_lines[line_index].type = :added
+ end
+
+ # new
+ if line_index == 0 && diff.deleted_file
+ new_lines[line_index].type = :file_deleted
+ new_lines[line_index].content = "File was deleted"
+ elsif added_line
+ new_lines[line_index].type = :added
+ new_lines[line_index].num = line_index2 + 1
+ new_lines[line_index].content = new_line
+ new_lines[line_index].code = added_line[:line_code]
+ elsif new_line
+ new_lines[line_index].type = :no_change
+ new_lines[line_index].num = line_index2 + 1
+ new_lines[line_index].content = new_line
+ else
+ new_lines[line_index].type = :deleted
+ end
+ end
+
+ return old_lines, new_lines
+ end
+
protected
# Private: Returns a link to a person. If the person has a matching user and
@@ -118,15 +195,18 @@ module CommitsHelper
def commit_person_link(commit, options = {})
source_name = commit.send "#{options[:source]}_name".to_sym
source_email = commit.send "#{options[:source]}_email".to_sym
+
+ user = User.find_for_commit(source_email, source_name)
+ person_name = user.nil? ? source_name : user.name
+ person_email = user.nil? ? source_email : user.email
+
text = if options[:avatar]
- avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
- %Q{#{avatar} #{source_name}}
+ avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
+ %Q{#{avatar} #{person_name}}
else
- source_name
+ person_name
end
- user = User.where('name like ? or email like ?', source_name, source_email).first
-
options = {
class: "commit-#{options[:source]}-link has_tooltip",
data: { :'original-title' => sanitize(source_email) }
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index ea2540bf38..5ff19b8829 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -1,6 +1,8 @@
module CompareHelper
def compare_to_mr_button?
- params[:from].present? && params[:to].present? &&
+ @project.merge_requests_enabled &&
+ params[:from].present? &&
+ params[:to].present? &&
@repository.branch_names.include?(params[:from]) &&
@repository.branch_names.include?(params[:to]) &&
params[:from] != params[:to] &&
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 35c7bcbd2c..d5712ab337 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,7 +1,8 @@
module DashboardHelper
def filter_path(entity, options={})
exist_opts = {
- status: params[:status],
+ state: params[:state],
+ scope: params[:scope],
project_id: params[:project_id],
}
@@ -12,18 +13,26 @@ module DashboardHelper
path
end
- def entities_per_project project, entity
- items = project.items_for(entity)
+ def entities_per_project(project, entity)
+ case entity.to_sym
+ when :issue then @issues.where(project_id: project.id)
+ when :merge_request then @merge_requests.where(target_project_id: project.id)
+ else
+ []
+ end.count
+ end
- items = case params[:status]
- when 'closed'
- items.closed
- when 'all'
- items
- else
- items.opened
- end
+ def projects_dashboard_filter_path(options={})
+ exist_opts = {
+ sort: params[:sort],
+ scope: params[:scope],
+ group: params[:group],
+ }
- items.cared(current_user).count
+ options = exist_opts.merge(options)
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 4aeb65752f..929f9a9c38 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -90,27 +90,23 @@ module EventsHelper
if event.note? && event.note_commit?
project_commit_path(event.project, event.note_target)
else
- url_for([event.project, event.note_target])
+ polymorphic_path([event.project, event.note_target], anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
if event.note_commit?
- link_to project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" do
+ link_to project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)), class: "commit_short_id" do
"#{event.note_target_type} #{event.note_short_commit_id}"
end
elsif event.note_project_snippet?
link_to(project_snippet_path(event.project, event.note_target)) do
- content_tag :strong do
- "#{event.note_target_type} ##{truncate event.note_target_id}"
- end
+ "#{event.note_target_type} ##{truncate event.note_target_id}"
end
else
link_to event_note_target_path(event) do
- content_tag :strong do
- "#{event.note_target_type} ##{truncate event.note_target_iid}"
- end
+ "#{event.note_target_type} ##{truncate event.note_target_iid}"
end
end
elsif event.wall_note?
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index b34f703353..69425bc171 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -28,14 +28,15 @@ module GitlabMarkdownHelper
link_to(gfm_body.html_safe, url, html_options)
end
- def markdown(text)
- unless @markdown
- gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
- # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
- filter_html: true,
- with_toc_data: true,
- hard_wrap: true,
- safe_links_only: true)
+ def markdown(text, options={})
+ unless (@markdown and options == @options)
+ @options = options
+ gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, {
+ # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
+ filter_html: true,
+ with_toc_data: true,
+ safe_links_only: true
+ }.merge(options))
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
no_intra_emphasis: true,
@@ -43,11 +44,10 @@ module GitlabMarkdownHelper
fenced_code_blocks: true,
autolink: true,
strikethrough: true,
- lax_html_blocks: true,
+ lax_spacing: true,
space_after_headers: true,
superscript: true)
end
-
@markdown.render(text).html_safe
end
@@ -63,14 +63,22 @@ module GitlabMarkdownHelper
# project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq
# ref - name of the branch or reference, eg. stable
# requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from
- # wiki - whether the markdown is from wiki or not
- def create_relative_links(text, project_path_with_namespace, ref, requested_path, wiki = false)
+ def create_relative_links(text, project, ref, requested_path)
+ @path_to_satellite = project.satellite.path
+ project_path_with_namespace = project.path_with_namespace
paths = extract_paths(text)
paths.each do |file_path|
- new_path = rebuild_path(project_path_with_namespace, file_path, requested_path, ref)
- # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word
- # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test
- text.gsub!("](#{file_path})", "](/#{new_path})")
+ original_file_path = extract(file_path)
+ new_path = rebuild_path(project_path_with_namespace, original_file_path, requested_path, ref)
+ if reference_path?(file_path)
+ # Replacing old string with a new one that contains updated path
+ # eg. [some document]: document.md will be replaced with [some document] /namespace/project/master/blob/document.md
+ text.gsub!(file_path, file_path.gsub(original_file_path, "/#{new_path}"))
+ else
+ # Replacing old string with a new one with brackets ]() to prevent replacing occurence of a word
+ # e.g. If we have a markdown like [test](test) this will replace ](test) and not the word test
+ text.gsub!("](#{file_path})", "](/#{new_path})")
+ end
end
text
end
@@ -81,9 +89,11 @@ module GitlabMarkdownHelper
select_relative(paths)
end
- # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text")
+ # Split the markdown text to each line and find all paths, this will match anything with - ]("some_text") and [some text]: file.md
def pick_out_paths(markdown_text)
- markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) }
+ inline_paths = markdown_text.split("\n").map { |text| text.scan(/\]\(([^(]+)\)/) }
+ reference_paths = markdown_text.split("\n").map { |text| text.scan(/\[.*\]:.*/) }
+ inline_paths + reference_paths
end
# Removes any empty result produced by not matching the regexp
@@ -91,32 +101,44 @@ module GitlabMarkdownHelper
paths.reject{|l| l.empty? }.flatten
end
+ # If a path is a reference style link we need to omit ]:
+ def extract(path)
+ path.split("]: ").last
+ end
+
# Reject any path that contains ignored protocol
# eg. reject "https://gitlab.org} but accept "doc/api/README.md"
def select_relative(paths)
paths.reject{|path| ignored_protocols.map{|protocol| path.include?(protocol)}.any?}
end
+ # Check whether a path is a reference-style link
+ def reference_path?(path)
+ path.include?("]: ")
+ end
+
def ignored_protocols
["http://","https://", "ftp://", "mailto:"]
end
def rebuild_path(path_with_namespace, path, requested_path, ref)
+ path.gsub!(/(#.*)/, "")
+ id = $1 || ""
file_path = relative_file_path(path, requested_path)
[
path_with_namespace,
path_with_ref(file_path, ref),
file_path
- ].compact.join("/")
+ ].compact.join("/").gsub(/\/*$/, '') + id
end
# Checks if the path exists in the repo
- # eg. checks if doc/README.md exists, if it doesn't then it is a wiki link
+ # eg. checks if doc/README.md exists, if not then link to blob
def path_with_ref(path, ref)
if file_exists?(path)
"#{local_path(path)}/#{correct_ref(ref)}"
else
- "wikis"
+ "blob/#{correct_ref(ref)}"
end
end
@@ -132,6 +154,7 @@ module GitlabMarkdownHelper
# If we are at doc/api and the README.md shown in below the tree view
# this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md
def build_nested_path(path, request_path)
+ return request_path if path == ""
return path unless request_path
if local_path(request_path) == "tree"
base = request_path.split("/").push(path)
@@ -144,14 +167,28 @@ module GitlabMarkdownHelper
end
def file_exists?(path)
- return false if path.nil? || path.empty?
- File.exists?(Rails.root.join(path))
+ return false if path.nil?
+ return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
end
# Check if the path is pointing to a directory(tree) or a file(blob)
# eg. doc/api is directory and doc/README.md is file
def local_path(path)
- File.directory?(Rails.root.join(path)) ? "tree" : "blob"
+ return "tree" if @repository.tree(current_sha, path).entries.any?
+ return "raw" if @repository.blob_at(current_sha, path).image?
+ return "blob"
+ end
+
+ def current_ref
+ @commit.nil? ? "master" : @commit.id
+ end
+
+ def current_sha
+ if @commit
+ @commit.id
+ else
+ @repository.head_commit.sha
+ end
end
# We will assume that if no ref exists we can point to master
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 8573c59dc9..cfc9a572ca 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,5 +1,36 @@
module GroupsHelper
def remove_user_from_group_message(group, user)
- "You are going to remove #{user.name} from #{group.name} Group. Are you sure?"
+ "Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?"
+ end
+
+ def leave_group_message(group)
+ "Are you sure you want to leave \"#{group}\" group?"
+ end
+
+ def should_user_see_group_roles?(user, group)
+ if user
+ user.is_admin? || group.members.exists?(user_id: user.id)
+ else
+ false
+ end
+ end
+
+ def group_head_title
+ title = @group.name
+
+ title = if current_action?(:issues)
+ "Issues - " + title
+ elsif current_action?(:merge_requests)
+ "Merge requests - " + title
+ elsif current_action?(:members)
+ "Members - " + title
+ elsif current_action?(:edit)
+ "Settings - " + title
+ else
+ title
+ end
+
+ title
+
end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
new file mode 100644
index 0000000000..53d4a8f2e6
--- /dev/null
+++ b/app/helpers/icons_helper.rb
@@ -0,0 +1,21 @@
+module IconsHelper
+ def boolean_to_icon(value)
+ if value.to_s == "true"
+ content_tag :i, nil, class: 'icon-ok cgreen'
+ else
+ content_tag :i, nil, class: 'icon-off clgray'
+ end
+ end
+
+ def public_icon
+ content_tag :i, nil, class: 'icon-globe'
+ end
+
+ def internal_icon
+ content_tag :i, nil, class: 'icon-shield'
+ end
+
+ def private_icon
+ content_tag :i, nil, class: 'icon-lock'
+ end
+end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2221583912..7c58908165 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -68,4 +68,32 @@ module IssuesHelper
false
end
end
+
+ def bulk_update_milestone_options
+ options_for_select(["None (backlog)"]) + options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id])
+ end
+
+ def bulk_update_assignee_options
+ options_for_select(["None (unassigned)"]) + options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id])
+ end
+
+ def assignee_options object
+ options_from_collection_for_select(@project.team.members.sort_by(&:name), 'id', 'name', object.assignee_id)
+ end
+
+ def milestone_options object
+ options_from_collection_for_select(object.project.milestones.active, 'id', 'title', object.milestone_id)
+ end
+
+ def issue_box_class(item)
+ if item.respond_to?(:expired?) && item.expired?
+ 'issue-box-expired'
+ elsif item.respond_to?(:merged?) && item.merged?
+ 'issue-box-merged'
+ elsif item.closed?
+ 'issue-box-closed'
+ else
+ 'issue-box-open'
+ end
+ end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 9a6aea85ee..4d20b827a0 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -12,15 +12,15 @@ module LabelsHelper
def label_css_class(name)
klass = Gitlab::IssuesLabels
- case name
+ case name.downcase
when *klass.warning_labels
'label-warning'
when *klass.neutral_labels
- 'label-inverse'
+ 'label-primary'
when *klass.positive_labels
'label-success'
when *klass.important_labels
- 'label-important'
+ 'label-danger'
else
'label-info'
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 4ba48aa433..00ec34ae54 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,9 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
+ target_project = event.project.forked_from_project || event.project
new_project_merge_request_path(
event.project,
- new_mr_from_push_event(event, event.project)
+ new_mr_from_push_event(event, target_project)
)
end
@@ -19,7 +20,7 @@ module MergeRequestsHelper
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref,
- title: event.branch_name.titleize
+ title: event.branch_name.titleize.humanize
}
end
@@ -36,7 +37,7 @@ module MergeRequestsHelper
def merge_path_description(merge_request, separator)
if merge_request.for_fork?
- "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
+ "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
else
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index dc88e17836..c363c7ffd7 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,7 +1,7 @@
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
- groups = current_user.owned_groups.select {|n| n.type == 'Group'}
- users = current_user.namespaces.reject {|n| n.type == 'Group'}
+ groups = current_user.owned_groups
+ users = [current_user.namespace]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
@@ -16,4 +16,13 @@ module NamespacesHelper
grouped_options_for_select(options, selected)
end
+
+ def namespace_select_tag(id, opts = {})
+ css_class = "ajax-namespace-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
+ end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index a3ec4cca59..7ae104b8fd 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,7 +1,7 @@
module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
- (@target_type.camelize == note.noteable_type && !note.for_diff_line?)
+ (@noteable.class.name == note.noteable_type && !note.for_diff_line?)
end
def note_target_fields
@@ -17,22 +17,29 @@ module NotesHelper
def link_to_merge_request_diff_line_note(note)
if note.for_merge_request_diff_line? and note.diff
- link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable_id, anchor: note.line_code)
+ link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code)
end
end
- def loading_more_notes?
- params[:loading_more].present?
- end
-
- def loading_new_notes?
- params[:loading_new].present?
- end
-
def note_timestamp(note)
# Shows the created at time and the updated at time if different
- ts = "#{time_ago_in_words(note.created_at)} ago"
- ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at
+ ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
+ if note.updated_at != note.created_at
+ ts << capture_haml do
+ haml_tag :small do
+ haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')})"
+ end
+ end
+ end
ts.html_safe
end
+
+ def noteable_json(noteable)
+ {
+ id: noteable.id,
+ class: noteable.class.name,
+ resources: noteable.class.table_name,
+ project_id: noteable.project.id,
+ }.to_json
+ end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index ae3402b261..b2399bb6db 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,11 +1,11 @@
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
- content_tag :i, nil, class: 'icon-circle cred'
+ content_tag :i, nil, class: 'icon-volume-off cred'
elsif notification.participating?
- content_tag :i, nil, class: 'icon-circle cblue'
+ content_tag :i, nil, class: 'icon-volume-down cblue'
elsif notification.watch?
- content_tag :i, nil, class: 'icon-circle cgreen'
+ content_tag :i, nil, class: 'icon-volume-up cgreen'
else
content_tag :i, nil, class: 'icon-circle-blank cblue'
end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index dd9e03d95a..297ae83d89 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -10,7 +10,7 @@ module ProfileHelper
end
def show_profile_social_tab?
- Gitlab.config.omniauth.enabled && !current_user.ldap_user?
+ enabled_social_providers.any? && !current_user.ldap_user?
end
def show_profile_remove_tab?
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 864f6c9af9..ef0460f872 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -5,7 +5,7 @@ module ProjectsHelper
def link_to_project project
link_to project do
- title = content_tag(:span, project.name, class: 'projet-name')
+ title = content_tag(:span, project.name, class: 'project-name')
if project.namespace
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
@@ -45,7 +45,10 @@ module ProjectsHelper
link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
end
else
- project.name
+ owner = project.namespace.owner
+ content_tag :span do
+ link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name
+ end
end
end
@@ -61,12 +64,39 @@ module ProjectsHelper
project_nav_tabs.include? name
end
+ def selected_label?(label_name)
+ params[:label_name].to_s.split(',').include?(label_name)
+ end
+
+ def labels_filter_path(label_name)
+ label_name =
+ if selected_label?(label_name)
+ params[:label_name].split(',').reject { |l| l == label_name }.join(',')
+ elsif params[:label_name].present?
+ "#{params[:label_name]},#{label_name}"
+ else
+ label_name
+ end
+
+ project_filter_path(label_name: label_name)
+ end
+
+ def label_filter_class(label_name)
+ if selected_label?(label_name)
+ 'label-filter-item active'
+ else
+ 'label-filter-item light'
+ end
+ end
+
def project_filter_path(options={})
exist_opts = {
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
+ assignee_id: params[:assignee_id],
+ sort: params[:sort],
}
options = exist_opts.merge(options)
@@ -77,10 +107,10 @@ module ProjectsHelper
end
def project_active_milestones
- @project.milestones.active.order("due_date, title ASC").all
+ @project.milestones.active.order("due_date, title ASC")
end
- def project_issues_trackers
+ def project_issues_trackers(current_tracker = nil)
values = Project.issues_tracker.values.map do |tracker_key|
if tracker_key.to_sym == :gitlab
['GitLab', tracker_key]
@@ -89,7 +119,7 @@ module ProjectsHelper
end
end
- options_for_select(values)
+ options_for_select(values, current_tracker)
end
private
@@ -132,12 +162,63 @@ module ProjectsHelper
end
end
- def repository_size
- "#{@project.repository.size} MB"
+ def repository_size(project = nil)
+ "#{(project || @project).repository_size} MB"
rescue
# In order to prevent 500 error
# when application cannot allocate memory
# to calculate repo size - just show 'Unknown'
'unknown'
end
+
+ def project_head_title
+ title = @project.name_with_namespace
+
+ title = if current_controller?(:tree)
+ "#{@project.path}\/#{@path} at #{@ref} - " + title
+ elsif current_controller?(:issues)
+ if current_action?(:show)
+ "Issue ##{@issue.iid} - #{@issue.title} - " + title
+ else
+ "Issues - " + title
+ end
+ elsif current_controller?(:blob)
+ "#{@project.path}\/#{@blob.path} at #{@ref} - " + title
+ elsif current_controller?(:commits)
+ "Commits at #{@ref} - " + title
+ elsif current_controller?(:merge_requests)
+ if current_action?(:show)
+ "Merge request ##{@merge_request.iid} - " + title
+ else
+ "Merge requests - " + title
+ end
+ elsif current_controller?(:wikis)
+ "Wiki - " + title
+ elsif current_controller?(:network)
+ "Network graph - " + title
+ elsif current_controller?(:graphs)
+ "Graphs - " + title
+ else
+ title
+ end
+
+ title
+ end
+
+ def default_url_to_repo(project = nil)
+ project = project || @project
+ current_user ? project.url_to_repo : project.http_url_to_repo
+ end
+
+ def default_clone_protocol
+ current_user ? "ssh" : "http"
+ end
+
+ def project_last_activity(project)
+ if project.last_activity_at
+ time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago')
+ else
+ "Never"
+ end
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 33c5e4fb9d..d7f3da7e53 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,14 +1,21 @@
module SearchHelper
- def search_autocomplete_source
+ def search_autocomplete_opts(term)
return unless current_user
+ resources_results = [
+ groups_autocomplete(term),
+ projects_autocomplete(term)
+ ].flatten
+
+ generic_results = project_autocomplete + default_autocomplete + help_autocomplete
+ generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
+
[
- groups_autocomplete,
- projects_autocomplete,
- default_autocomplete,
- project_autocomplete,
- help_autocomplete
- ].flatten.to_json
+ resources_results,
+ generic_results
+ ].flatten.uniq do |item|
+ item[:label]
+ end
end
private
@@ -41,7 +48,7 @@ module SearchHelper
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- prefix = simple_sanitize(@project.name_with_namespace)
+ prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
@@ -63,16 +70,26 @@ module SearchHelper
end
# Autocomplete results for the current user's groups
- def groups_autocomplete
- current_user.authorized_groups.map do |group|
- { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) }
+ def groups_autocomplete(term, limit = 5)
+ current_user.authorized_groups.search(term).limit(limit).map do |group|
+ {
+ label: "group: #{search_result_sanitize(group.name)}",
+ url: group_path(group)
+ }
end
end
# Autocomplete results for the current user's projects
- def projects_autocomplete
- current_user.authorized_projects.map do |p|
- { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
+ def projects_autocomplete(term, limit = 5)
+ Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
+ {
+ label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ url: project_path(p)
+ }
end
end
+
+ def search_result_sanitize(str)
+ Sanitize.clean(str)
+ end
end
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
new file mode 100644
index 0000000000..ab24367c45
--- /dev/null
+++ b/app/helpers/selects_helper.rb
@@ -0,0 +1,20 @@
+module SelectsHelper
+ def users_select_tag(id, opts = {})
+ css_class = "ajax-users-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
+ end
+
+ def project_users_select_tag(id, opts = {})
+ css_class = "ajax-project-users-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+ placeholder = opts[:placeholder] || 'Select user'
+ project_id = opts[:project_id] || @project.id
+ hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
+ end
+end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
new file mode 100755
index 0000000000..09e5c08e62
--- /dev/null
+++ b/app/helpers/submodule_helper.rb
@@ -0,0 +1,59 @@
+module SubmoduleHelper
+ include Gitlab::ShellAdapter
+
+ # links to files listing for submodule if submodule is a project on this server
+ def submodule_links(submodule_item)
+ url = @repository.submodule_url_for(@ref, submodule_item.path)
+
+ return url, nil unless url =~ /([^\/:]+\/[^\/]+\.git)\Z/
+
+ project = $1
+ project.chomp!('.git')
+
+ if self_url?(url, project)
+ return project_path(project), project_tree_path(project, submodule_item.id)
+ elsif relative_self_url?(url)
+ relative_self_links(url, submodule_item.id)
+ elsif github_dot_com_url?(url)
+ standard_links('github.com', project, submodule_item.id)
+ elsif gitlab_dot_com_url?(url)
+ standard_links('gitlab.com', project, submodule_item.id)
+ else
+ return url, nil
+ end
+ end
+
+ protected
+
+ def github_dot_com_url?(url)
+ url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/
+ end
+
+ def gitlab_dot_com_url?(url)
+ url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/
+ end
+
+ def self_url?(url, project)
+ return true if url == [ Gitlab.config.gitlab.url, '/', project, '.git' ].join('')
+ url == gitlab_shell.url_to_repo(project)
+ end
+
+ def relative_self_url?(url)
+ # (./)?(../repo.git) || (./)?(../../project/repo.git) )
+ url =~ /^((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\Z/ || url =~ /^((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\Z/
+ end
+
+ def standard_links(host, project, commit)
+ base = [ 'https://', host, '/', project ].join('')
+ return base, [ base, '/tree/', commit ].join('')
+ end
+
+ def relative_self_links(url, commit)
+ if url.scan(/(\.\.\/)/).size == 2
+ base = url[/([^\/]*\/[^\/]*)\.git/, 1]
+ else
+ base = [ @project.group.path, '/', url[/([^\/]*)\.git/, 1] ].join('')
+ end
+ return project_path(base), project_tree_path(base, commit)
+ end
+end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index ce67587226..bd373c5f3c 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -92,7 +92,12 @@ module TabHelper
def nav_tab key, value, &block
o = {}
o[:class] = ""
- o[:class] << " active" if params[key] == value
+
+ if value.nil?
+ o[:class] << " active" if params[key].blank?
+ else
+ o[:class] << " active" if params[key] == value
+ end
if block_given?
content_tag(:li, capture(&block), o)
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 2dbc1cffb1..f39d0081dc 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -40,7 +40,7 @@ module TreeHelper
# Returns boolean
def markup?(filename)
filename.downcase.end_with?(*%w(.textile .rdoc .org .creole
- .mediawiki .rst .asciidoc .pod))
+ .mediawiki .rst .adoc .asciidoc .pod))
end
def gitlab_markdown?(filename)
@@ -91,4 +91,12 @@ module TreeHelper
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
+
+ def editing_preview_title(filename)
+ if gitlab_markdown?(filename) || markup?(filename)
+ 'Preview'
+ else
+ 'Diff'
+ end
+ end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
new file mode 100644
index 0000000000..81e10f3685
--- /dev/null
+++ b/app/helpers/visibility_level_helper.rb
@@ -0,0 +1,49 @@
+module VisibilityLevelHelper
+ def visibility_level_color(level)
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ 'cgreen'
+ when Gitlab::VisibilityLevel::INTERNAL
+ 'camber'
+ when Gitlab::VisibilityLevel::PUBLIC
+ 'cblue'
+ end
+ end
+
+ def visibility_level_description(level)
+ capture_haml do
+ haml_tag :span do
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ haml_concat "Project access must be granted explicitly for each user."
+ when Gitlab::VisibilityLevel::INTERNAL
+ haml_concat "The project can be cloned by"
+ haml_concat "any logged in user."
+ when Gitlab::VisibilityLevel::PUBLIC
+ haml_concat "The project can be cloned"
+ haml_concat "without any"
+ haml_concat "authentication."
+ end
+ end
+ end
+ end
+
+ def visibility_level_icon(level)
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ private_icon
+ when Gitlab::VisibilityLevel::INTERNAL
+ internal_icon
+ when Gitlab::VisibilityLevel::PUBLIC
+ public_icon
+ end
+ end
+
+ def visibility_level_label(level)
+ Project.visibility_levels.key(level)
+ end
+
+ def restricted_visibility_levels
+ current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels
+ end
+end
diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb
index 2e9d28981e..1654fc55bc 100644
--- a/app/mailers/emails/groups.rb
+++ b/app/mailers/emails/groups.rb
@@ -3,9 +3,9 @@ module Emails
def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id)
@group = @membership.group
-
+ @target_url = group_url(@group)
mail(to: @membership.user.email,
- subject: subject("access to group was granted"))
+ subject: subject("Access to group was granted"))
end
end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 6eda88c792..a096df9dc0 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -3,22 +3,33 @@ module Emails
def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ set_message_id("issue_#{issue_id}")
+ mail(from: sender(@issue.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
- def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
+ def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
@issue = Issue.find(issue_id)
- @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
+ @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @issue.project
- mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ set_reference("issue_#{issue_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
@issue = Issue.find issue_id
@project = @issue.project
@updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id),
- subject: subject("Closed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ set_reference("issue_#{issue_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@@ -26,8 +37,11 @@ module Emails
@issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id),
- subject: subject("changed issue ##{@issue.iid}", @issue.title))
+ @target_url = project_issue_url(@project, @issue)
+ set_reference("issue_#{issue_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 57c1925fa4..ea5671c450 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -2,24 +2,44 @@ module Emails
module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
- mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.iid}", @merge_request.title))
+ @project = @merge_request.project
+ @target_url = project_merge_request_url(@project, @merge_request)
+ set_message_id("merge_request_#{merge_request_id}")
+ mail(from: sender(@merge_request.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
- def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
+ def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
- @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
- mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.iid}", @merge_request.title))
+ @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
+ @project = @merge_request.project
+ @target_url = project_merge_request_url(@project, @merge_request)
+ set_reference("merge_request_#{merge_request_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@updated_by = User.find updated_by_user_id
- mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.iid}", @merge_request.title))
+ @project = @merge_request.project
+ @target_url = project_merge_request_url(@project, @merge_request)
+ set_reference("merge_request_#{merge_request_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
- def merged_merge_request_email(recipient_id, merge_request_id)
+ def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
- mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.iid}", @merge_request.title))
+ @project = @merge_request.project
+ @target_url = project_merge_request_url(@project, @merge_request)
+ set_reference("merge_request_#{merge_request_id}")
+ mail(from: sender(updated_by_user_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
end
@@ -53,7 +73,7 @@ module Emails
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = "GitLab Merge Request |"
+ subject = "Merge Request | "
if @merge_request.for_fork?
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
else
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index ba4f0dd862..8d1f17b0f8 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,27 +4,41 @@ module Emails
@note = Note.find(note_id)
@commit = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
+ @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@commit.title} (#{@commit.short_id})"))
end
def note_issue_email(recipient_id, note_id)
@note = Note.find(note_id)
@issue = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.iid}"))
+ @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
+ set_reference("issue_#{@issue.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@issue.title} (##{@issue.iid})"))
end
def note_merge_request_email(recipient_id, note_id)
@note = Note.find(note_id)
@merge_request = @note.noteable
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("note for merge request ##{@merge_request.iid}"))
+ @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
+ set_reference("merge_request_#{@merge_request.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
def note_wall_email(recipient_id, note_id)
@note = Note.find(note_id)
@project = @note.project
- mail(to: recipient(recipient_id), subject: subject("note on wall"))
+ @target_url = project_wall_url(@note.project, anchor: "note_#{@note.id}")
+ mail(from: sender(@note.author_id),
+ to: recipient(recipient_id),
+ subject: subject("Note on wall"))
end
end
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index bcd44f9476..f02d95fd55 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -3,12 +3,20 @@ module Emails
def new_user_email(user_id, password)
@user = User.find(user_id)
@password = password
+ @target_url = user_url(@user)
mail(to: @user.email, subject: subject("Account was created for you"))
end
+ def new_email_email(email_id)
+ @email = Email.find(email_id)
+ @user = @email.user
+ mail(to: @user.email, subject: subject("Email was added to your account"))
+ end
+
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
+ @target_url = user_url(@user)
mail(to: @user.email, subject: subject("SSH key was added to your account"))
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 4d5fe9ef61..9f99c11ea3 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,15 +3,35 @@ module Emails
def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
+ @target_url = project_url(@project)
mail(to: @users_project.user.email,
- subject: subject("access to project was granted"))
+ subject: subject("Access to project was granted"))
end
def project_was_moved_email(project_id, user_id)
@user = User.find user_id
@project = Project.find project_id
+ @target_url = project_url(@project)
mail(to: @user.email,
- subject: subject("project was moved"))
+ subject: subject("Project was moved"))
+ end
+
+ def repository_push_email(project_id, recipient, author_id, branch, compare)
+ @project = Project.find(project_id)
+ @author = User.find(author_id)
+ @compare = compare
+ @commits = Commit.decorate(compare.commits)
+ @diffs = compare.diffs
+ @branch = branch
+ if @commits.length > 1
+ @target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
+ else
+ @target_url = project_commit_url(@project, @commits.first)
+ end
+
+ mail(from: sender(author_id),
+ to: recipient,
+ subject: subject("New push to repository"))
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 2f7be00c33..84a0da0129 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -12,18 +12,36 @@ class Notify < ActionMailer::Base
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_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
- default from: Gitlab.config.gitlab.email_from
+ default from: Proc.new { default_sender_address.format }
+ default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
- # Just send email with 3 seconds delay
+ # Just send email with 2 seconds delay
def self.delay
delay_for(2.seconds)
end
private
+ # The default email address to send emails from
+ def default_sender_address
+ address = Mail::Address.new(Gitlab.config.gitlab.email_from)
+ address.display_name = "GitLab"
+ address
+ end
+
+ # Return an email address that displays the name of the sender.
+ # Only the displayed name changes; the actual email address is always the same.
+ def sender(sender_id)
+ if sender = User.find(sender_id)
+ address = default_sender_address
+ address.display_name = sender.name
+ address.format
+ end
+ end
+
# Look up a User by their ID and return their email address
#
# recipient_id - User ID
@@ -35,6 +53,22 @@ class Notify < ActionMailer::Base
end
end
+ # Set the Message-ID header field
+ #
+ # local_part - The local part of the message ID
+ #
+ def set_message_id(local_part)
+ headers["Message-ID"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
+ end
+
+ # Set the References header field
+ #
+ # local_part - The local part of the referenced message ID
+ #
+ def set_reference(local_part)
+ headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
+ end
+
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
@@ -42,21 +76,21 @@ class Notify < ActionMailer::Base
# Examples
#
# >> subject('Lorem ipsum')
- # => "GitLab | Lorem ipsum"
+ # => "Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #
# >> subject('Lorem ipsum')
- # => "GitLab | Ruby on Rails | Lorem ipsum "
+ # => "Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
- # => "GitLab | Lorem ipsum | Dolor sit amet"
+ # => "Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = "GitLab"
- subject << (@project ? " | #{@project.name_with_namespace}" : "")
- subject << " | " + extra.join(' | ') if extra.present?
+ subject = ""
+ subject << "#{@project.name} | " if @project
+ subject << extra.join(' | ') if extra.present?
subject
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 8547608914..1afe8a4638 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -14,6 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
+ when "UsersGroup" then users_group_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
@@ -29,7 +30,7 @@ class Ability
nil
end
- if project && project.public
+ if project && project.public?
[
:read_project,
:read_wiki,
@@ -42,7 +43,19 @@ class Ability
:download_code
]
else
- []
+ group = if subject.kind_of?(Group)
+ subject
+ elsif subject.respond_to?(:group)
+ subject.group
+ else
+ nil
+ end
+
+ if group && group.has_projects_accessible_to?(nil)
+ [:read_group]
+ else
+ []
+ end
end
end
@@ -59,37 +72,41 @@ class Ability
# Rules based on role in project
if team.masters.include?(user)
- rules << project_master_rules
+ rules += project_master_rules
elsif team.developers.include?(user)
- rules << project_dev_rules
+ rules += project_dev_rules
elsif team.reporters.include?(user)
- rules << project_report_rules
+ rules += project_report_rules
elsif team.guests.include?(user)
- rules << project_guest_rules
+ rules += project_guest_rules
end
- if project.public?
- rules << public_project_rules
+ if project.public? || project.internal?
+ rules += public_project_rules
end
if project.owner == user || user.admin?
- rules << project_admin_rules
+ rules += project_admin_rules
end
if project.group && project.group.has_owner?(user)
- rules << project_admin_rules
+ rules += project_admin_rules
end
- rules.flatten
+ if project.archived?
+ rules -= project_archived_rules
+ end
+
+ rules
end
def public_project_rules
project_guest_rules + [
:download_code,
- :fork_project,
+ :fork_project
]
end
@@ -121,10 +138,22 @@ class Ability
project_report_rules + [
:write_merge_request,
:write_wiki,
+ :modify_issue,
+ :admin_issue,
:push_code
]
end
+ def project_archived_rules
+ [
+ :write_merge_request,
+ :push_code,
+ :push_code_to_protected_branches,
+ :modify_merge_request,
+ :admin_merge_request
+ ]
+ end
+
def project_master_rules
project_dev_rules + [
:push_code_to_protected_branches,
@@ -145,22 +174,23 @@ class Ability
def project_admin_rules
project_master_rules + [
:change_namespace,
- :change_public_mode,
+ :change_visibility_level,
:rename_project,
- :remove_project
+ :remove_project,
+ :archive_project
]
end
def group_abilities user, group
rules = []
- if group.users.include?(user) || user.admin?
+ if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
rules << :read_group
end
# Only group owner and administrators can manage group
if group.has_owner?(user) || user.admin?
- rules << [
+ rules += [
:manage_group,
:manage_namespace
]
@@ -174,7 +204,7 @@ class Ability
# Only namespace owner and administrators can manage it
if namespace.owner == user || user.admin?
- rules << [
+ rules += [
:manage_namespace
]
end
@@ -202,5 +232,20 @@ class Ability
end
end
end
+
+ def users_group_abilities(user, subject)
+ rules = []
+ target_user = subject.user
+ group = subject.group
+ can_manage = group_abilities(user, group).include?(:manage_group)
+ if can_manage && (user != target_user)
+ rules << :modify
+ rules << :destroy
+ end
+ if !group.last_owner?(user) && (can_manage || (user == target_user))
+ rules << :destroy
+ end
+ rules
+ end
end
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
new file mode 100644
index 0000000000..85ab7ed6ae
--- /dev/null
+++ b/app/models/broadcast_message.rb
@@ -0,0 +1,29 @@
+# == Schema Information
+#
+# Table name: broadcast_messages
+#
+# id :integer not null, primary key
+# message :text not null
+# starts_at :datetime
+# ends_at :datetime
+# alert_type :integer
+# created_at :datetime
+# updated_at :datetime
+# color :string(255)
+# font :string(255)
+#
+
+class BroadcastMessage < ActiveRecord::Base
+ attr_accessible :alert_type, :color, :ends_at, :font, :message, :starts_at
+
+ validates :message, presence: true
+ validates :starts_at, presence: true
+ validates :ends_at, presence: true
+
+ validates :color, format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ }, allow_blank: true
+ validates :font, format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ }, allow_blank: true
+
+ def self.current
+ where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index dd1f980187..c313aeb757 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -16,29 +16,31 @@ class Commit
DIFF_HARD_LIMIT_FILES = 500
DIFF_HARD_LIMIT_LINES = 10000
- def self.decorate(commits)
- commits.map { |c| self.new(c) }
- end
+ class << self
+ def decorate(commits)
+ commits.map { |c| self.new(c) }
+ end
- # Calculate number of lines to render for diffs
- def self.diff_line_count(diffs)
- diffs.reduce(0){|sum, d| sum + d.diff.lines.count}
- end
+ # Calculate number of lines to render for diffs
+ def diff_line_count(diffs)
+ diffs.reduce(0){|sum, d| sum + d.diff.lines.count}
+ end
- def self.diff_suppress?(diffs, line_count = nil)
- # optimize - check file count first
- return true if diffs.size > DIFF_SAFE_FILES
+ def diff_suppress?(diffs, line_count = nil)
+ # optimize - check file count first
+ return true if diffs.size > DIFF_SAFE_FILES
- line_count ||= Commit::diff_line_count(diffs)
- line_count > DIFF_SAFE_LINES
- end
+ line_count ||= Commit::diff_line_count(diffs)
+ line_count > DIFF_SAFE_LINES
+ end
- def self.diff_force_suppress?(diffs, line_count = nil)
- # optimize - check file count first
- return true if diffs.size > DIFF_HARD_LIMIT_FILES
+ def diff_force_suppress?(diffs, line_count = nil)
+ # optimize - check file count first
+ return true if diffs.size > DIFF_HARD_LIMIT_FILES
- line_count ||= Commit::diff_line_count(diffs)
- line_count > DIFF_HARD_LIMIT_LINES
+ line_count ||= Commit::diff_line_count(diffs)
+ line_count > DIFF_HARD_LIMIT_LINES
+ end
end
attr_accessor :raw
@@ -97,14 +99,16 @@ class Commit
#
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description
- description = safe_message
+ title_end = safe_message.index(/\n/)
+ @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
+ "…".html_safe << safe_message[80..-1]
+ else
+ safe_message.split(/\n/, 2)[1].try(:chomp)
+ end
+ end
- title_end = description.index(/\n/)
- if (!title_end && description.length > 100) || (title_end && title_end > 100)
- "…".html_safe << description[80..-1]
- else
- description.split(/\n/, 2)[1].try(:chomp)
- end
+ def description?
+ description.present?
end
# Regular expression that identifies commit message clauses that trigger issue closing.
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7f820f950b..de167f8803 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -23,7 +23,8 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
-
+ scope :opened, -> { with_state(:opened, :reopened) }
+ scope :closed, -> { with_state(:closed) }
delegate :name,
:email,
@@ -36,8 +37,6 @@ module Issuable
allow_nil: true,
prefix: true
- attr_accessor :author_id_of_changes
-
attr_mentionable :title, :description
end
@@ -45,6 +44,18 @@ module Issuable
def search(query)
where("title like :query", query: "%#{query}%")
end
+
+ def sort(method)
+ case method.to_s
+ when 'newest' then reorder("#{table_name}.created_at DESC")
+ when 'oldest' then reorder("#{table_name}.created_at ASC")
+ when 'recently_updated' then reorder("#{table_name}.updated_at DESC")
+ when 'last_updated' then reorder("#{table_name}.updated_at ASC")
+ when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC")
+ when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC")
+ else reorder("#{table_name}.created_at DESC")
+ end
+ end
end
def today?
@@ -111,4 +122,11 @@ module Issuable
end
users.concat(mentions.reduce([], :|)).uniq
end
+
+ def to_hook_data
+ {
+ object_kind: self.class.name.underscore,
+ object_attributes: self.attributes
+ }
+ end
end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 47aeb93a41..570f5e91c1 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -4,8 +4,8 @@
#
# id :integer not null, primary key
# user_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# key :text
# title :string(255)
# type :string(255)
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 6f109e4831..739d749830 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -5,8 +5,8 @@
# id :integer not null, primary key
# deploy_key_id :integer not null
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
#
class DeployKeysProject < ActiveRecord::Base
diff --git a/app/models/diff_line.rb b/app/models/diff_line.rb
new file mode 100644
index 0000000000..ad37945874
--- /dev/null
+++ b/app/models/diff_line.rb
@@ -0,0 +1,3 @@
+class DiffLine
+ attr_accessor :type, :content, :num, :code
+end
diff --git a/app/models/email.rb b/app/models/email.rb
new file mode 100644
index 0000000000..9068c2b87b
--- /dev/null
+++ b/app/models/email.rb
@@ -0,0 +1,41 @@
+# == Schema Information
+#
+# Table name: emails
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# email :string(255) not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Email < ActiveRecord::Base
+ attr_accessible :email, :user_id
+
+ #
+ # Relations
+ #
+ belongs_to :user
+
+ #
+ # Validations
+ #
+ validates :user_id, presence: true
+ validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
+ validate :unique_email, if: ->(email) { email.email_changed? }
+
+ after_create :notify
+ before_validation :cleanup_email
+
+ def cleanup_email
+ self.email = self.email.downcase.strip
+ end
+
+ def unique_email
+ self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
+ end
+
+ def notify
+ NotificationService.new.new_email(self)
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 095a06c956..cf88e9f4af 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -8,8 +8,8 @@
# title :string(255)
# data :text
# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# action :integer
# author_id :integer
#
@@ -18,7 +18,7 @@ class Event < ActiveRecord::Base
attr_accessible :project, :action, :data, :author_id, :project_id,
:target_id, :target_type
- default_scope where("author_id IS NOT NULL")
+ default_scope { where.not(author_id: nil) }
CREATED = 1
UPDATED = 2
@@ -47,20 +47,14 @@ class Event < ActiveRecord::Base
scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
class << self
- def determine_action(record)
- if [Issue, MergeRequest].include? record.class
- Event::CREATED
- elsif record.kind_of? Note
- Event::COMMENTED
- end
- end
-
def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads')
+ commit = project.repository.commit(ref.target)
+
if action.to_s == 'add'
before = '00000000'
- after = ref.commit.id
+ after = commit.id
else
- before = ref.commit.id
+ before = commit.id
after = '00000000'
end
@@ -168,7 +162,7 @@ class Event < ActiveRecord::Base
end
def valid_push?
- data[:ref]
+ data[:ref] && ref_name.present?
rescue => ex
false
end
@@ -223,7 +217,7 @@ class Event < ActiveRecord::Base
# Max 20 commits from push DESC
def commits
- @commits ||= data[:commits].reverse
+ @commits ||= (data[:commits] || []).reverse
end
def commits_count
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index aaa527a114..17add270f6 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -5,8 +5,8 @@
# id :integer not null, primary key
# forked_to_project_id :integer not null
# forked_from_project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
#
class ForkedProjectLink < ActiveRecord::Base
diff --git a/app/models/group.rb b/app/models/group.rb
index 0ee058be15..3cbf30a20d 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -6,16 +6,33 @@
# name :string(255) not null
# path :string(255) not null
# owner_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# type :string(255)
# description :string(255) default(""), not null
+# avatar :string(255)
#
+require 'carrierwave/orm/activerecord'
+require 'file_size_validator'
+
class Group < Namespace
has_many :users_groups, dependent: :destroy
has_many :users, through: :users_groups
+ attr_accessible :avatar
+
+ validate :avatar_type, if: ->(user) { user.avatar_changed? }
+ validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+
+ mount_uploader :avatar, AttachmentUploader
+
+ def self.accessible_to(user)
+ accessible_ids = Project.accessible_to(user).pluck(:namespace_id)
+ accessible_ids += user.groups.pluck(:id) if user
+ where(id: accessible_ids)
+ end
+
def human_name
name
end
@@ -26,7 +43,7 @@ class Group < Namespace
def add_users(user_ids, group_access)
user_ids.compact.each do |user_id|
- user = self.users_groups.find_or_initialize_by_user_id(user_id)
+ user = self.users_groups.find_or_initialize_by(user_id: user_id)
user.update_attributes(group_access: group_access)
end
end
@@ -50,4 +67,10 @@ class Group < Namespace
def members
users_groups
end
+
+ def avatar_type
+ unless self.avatar.image?
+ self.errors.add :avatar, "only images allowed"
+ end
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index f3ec322126..16d51345e5 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -7,8 +7,8 @@
# assignee_id :integer
# author_id :integer
# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# position :integer default(0)
# branch_name :string(255)
# description :text
@@ -21,17 +21,16 @@ class Issue < ActiveRecord::Base
include Issuable
include InternalId
+ ActsAsTaggableOn.strict_case_match = true
+
belongs_to :project
validates :project, presence: true
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
- scope :opened, -> { with_state(:opened) }
- scope :closed, -> { with_state(:closed) }
attr_accessible :title, :assignee_id, :position, :description,
- :milestone_id, :label_list, :author_id_of_changes,
- :state_event
+ :milestone_id, :label_list, :state_event
acts_as_taggable_on :labels
@@ -48,18 +47,27 @@ class Issue < ActiveRecord::Base
end
state :opened
-
state :reopened
-
state :closed
end
- # Both open and reopened issues should be listed as opened
- scope :opened, -> { with_state(:opened, :reopened) }
-
# Mentionable overrides.
def gfm_reference
"issue ##{iid}"
end
+
+ # Reset issue events cache
+ #
+ # Since we do cache @event we need to reset cache in special cases:
+ # * when an issue is updated
+ # Events cache stored like events/23-20130109142513.
+ # The cache key includes updated_at timestamp.
+ # Thus it will automatically generate a new fragment
+ # when the event is updated because the key changes.
+ def reset_events_cache
+ Event.where(target_id: self.id, target_type: 'Issue').
+ order('id DESC').limit(100).
+ update_all(updated_at: Time.now)
+ end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 79f7bbd259..035c9efa01 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -4,8 +4,8 @@
#
# id :integer not null, primary key
# user_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# key :text
# title :string(255)
# type :string(255)
@@ -29,6 +29,10 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true
+ after_create :add_to_shell
+ after_create :notify_user
+ after_destroy :remove_from_shell
+
def strip_white_space
self.key = key.strip unless key.blank?
end
@@ -42,6 +46,26 @@ class Key < ActiveRecord::Base
"key-#{id}"
end
+ def add_to_shell
+ GitlabShellWorker.perform_async(
+ :add_key,
+ shell_id,
+ key
+ )
+ end
+
+ def notify_user
+ NotificationService.new.new_key(self)
+ end
+
+ def remove_from_shell
+ GitlabShellWorker.perform_async(
+ :remove_key,
+ shell_id,
+ key,
+ )
+ end
+
private
def generate_fingerpint
@@ -53,7 +77,7 @@ class Key < ActiveRecord::Base
Tempfile.open('gitlab_key_file') do |file|
file.puts key
file.rewind
- cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp')
+ cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp')
end
if cmd_status.zero?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b164ea1107..061537132b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -9,10 +9,8 @@
# author_id :integer
# assignee_id :integer
# title :string(255)
-# created_at :datetime not null
-# updated_at :datetime not null
-# st_commits :text(2147483647)
-# st_diffs :text(2147483647)
+# created_at :datetime
+# updated_at :datetime
# milestone_id :integer
# state :string(255)
# merge_status :string(255)
@@ -31,30 +29,52 @@ class MergeRequest < ActiveRecord::Base
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
- attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description
+ has_one :merge_request_diff, dependent: :destroy
+
+ after_create :create_merge_request_diff
+ after_update :update_merge_request_diff
+
+ delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
+
+ attr_accessible :title, :assignee_id, :source_project_id, :source_branch,
+ :target_project_id, :target_branch, :milestone_id,
+ :state_event, :description, :label_list
attr_accessor :should_remove_source_branch
+ # When this attribute is true some MR validation is ignored
+ # It allows us to close or modify broken merge requests
+ attr_accessor :allow_broken
+
+ ActsAsTaggableOn.strict_case_match = true
+ acts_as_taggable_on :labels
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :merge do
- transition [:reopened, :opened] => :merged
+ transition [:reopened, :opened, :locked] => :merged
end
event :reopen do
transition closed: :reopened
end
+ event :lock do
+ transition [:reopened, :opened] => :locked
+ end
+
+ event :unlock do
+ transition locked: :reopened
+ end
+
state :opened
-
state :reopened
-
state :closed
-
state :merged
+ state :locked
end
state_machine :merge_status, initial: :unchecked do
@@ -71,25 +91,19 @@ class MergeRequest < ActiveRecord::Base
end
state :unchecked
-
state :can_be_merged
-
state :cannot_be_merged
end
- serialize :st_commits
- serialize :st_diffs
-
- validates :source_project, presence: true
+ validates :source_project, presence: true, unless: :allow_broken
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validate :validate_branches
+ validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
- scope :opened, -> { with_state(:opened) }
- scope :closed, -> { with_state(:closed) }
scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -101,7 +115,7 @@ class MergeRequest < ActiveRecord::Base
scope :closed, -> { with_states(:closed, :merged) }
def validate_branches
- if target_project==source_project && target_branch == source_branch
+ if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
end
@@ -115,9 +129,33 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def validate_fork
+ return true unless target_project && source_project
+
+ if target_project == source_project
+ true
+ else
+ # If source and target projects are different
+ # we should check if source project is actually a fork of target project
+ if source_project.forked_from?(target_project)
+ true
+ else
+ errors.add :base, "Source project is not a fork of target project"
+ end
+ end
+ end
+
+ def update_merge_request_diff
+ if source_branch_changed? || target_branch_changed?
+ reload_code
+ mark_as_unchecked
+ end
+ end
+
def reload_code
- self.reloaded_commits
- self.reloaded_diffs
+ if merge_request_diff && open?
+ merge_request_diff.reload_content
+ end
end
def check_if_can_be_merged
@@ -128,42 +166,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- def diffs
- @diffs ||= (load_diffs(st_diffs) || [])
- end
-
- def reloaded_diffs
- if opened? && unmerged_diffs.any?
- self.st_diffs = dump_diffs(unmerged_diffs)
- self.save
- end
- end
-
- def broken_diffs?
- diffs == broken_diffs
- rescue
- true
- end
-
- def valid_diffs?
- !broken_diffs?
- end
-
- def unmerged_diffs
- diffs = if for_fork?
- Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite
- else
- Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch)
- end
-
- diffs ||= []
- diffs
- end
-
- def last_commit
- commits.first
- end
-
def merge_event
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end
@@ -172,56 +174,19 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
- def commits
- load_commits(st_commits || [])
+ def automerge!(current_user, commit_message = nil)
+ MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
end
- def probably_merged?
- unmerged_commits.empty? &&
- commits.any? && opened?
- end
-
- def reloaded_commits
- if opened? && unmerged_commits.any?
- self.st_commits = dump_commits(unmerged_commits)
- save
-
- end
- commits
- end
-
- def unmerged_commits
- if for_fork?
- commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between
- else
- commits = target_project.repository.commits_between(self.target_branch, self.source_branch)
- end
-
- if commits.present?
- commits = Commit.decorate(commits).
- sort_by(&:created_at).
- reverse
- end
- commits
- end
-
- def merge!(user_id)
- self.author_id_of_changes = user_id
- self.merge
- end
-
- def automerge!(current_user)
- if Gitlab::Satellite::MergeAction.new(current_user, self).merge! && self.unmerged_commits.empty?
- self.merge!(current_user.id)
- true
- end
- rescue
- mark_as_unmergeable
- false
+ def open?
+ opened? || reopened?
end
def mr_and_commit_notes
- commit_ids = commits.map(&:id)
+ # Fetch comments only from last 100 commits
+ commits_for_notes_limit = 100
+ commit_ids = commits.last(commits_for_notes_limit).map(&:id)
+
project.notes.where(
"(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))",
mr_id: id,
@@ -243,16 +208,12 @@ class MergeRequest < ActiveRecord::Base
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
end
- def last_commit_short_sha
- @last_commit_short_sha ||= last_commit.sha[0..10]
- end
-
def for_fork?
target_project != source_project
end
def disallow_source_branch_removal?
- (source_project.root_ref? source_branch) || for_fork?
+ source_project.root_ref?(source_branch) || source_project.protected_branches.include?(source_branch)
end
def project
@@ -262,7 +223,7 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues
if target_branch == project.default_branch
- unmerged_commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
+ commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
else
[]
end
@@ -273,33 +234,90 @@ class MergeRequest < ActiveRecord::Base
"merge request !#{iid}"
end
- private
-
- def dump_commits(commits)
- commits.map(&:to_hash)
- end
-
- def load_commits(array)
- array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) }
- end
-
- def dump_diffs(diffs)
- if diffs == broken_diffs
- broken_diffs
- elsif diffs.respond_to?(:map)
- diffs.map(&:to_hash)
+ def target_project_path
+ if target_project
+ target_project.path_with_namespace
+ else
+ "(removed)"
end
end
- def load_diffs(raw)
- if raw == broken_diffs
- broken_diffs
- elsif raw.respond_to?(:map)
- raw.map { |hash| Gitlab::Git::Diff.new(hash) }
+ def source_project_path
+ if source_project
+ source_project.path_with_namespace
+ else
+ "(removed)"
end
end
- def broken_diffs
- [Gitlab::Git::Diff::BROKEN_DIFF]
+ def source_project_namespace
+ if source_project && source_project.namespace
+ source_project.namespace.path
+ else
+ "(removed)"
+ end
+ end
+
+ def target_project_namespace
+ if target_project && target_project.namespace
+ target_project.namespace.path
+ else
+ "(removed)"
+ end
+ end
+
+ def source_branch_exists?
+ return false unless self.source_project
+
+ self.source_project.repository.branch_names.include?(self.source_branch)
+ end
+
+ def target_branch_exists?
+ return false unless self.target_project
+
+ self.target_project.repository.branch_names.include?(self.target_branch)
+ end
+
+ # Reset merge request events cache
+ #
+ # Since we do cache @event we need to reset cache in special cases:
+ # * when a merge request is updated
+ # Events cache stored like events/23-20130109142513.
+ # The cache key includes updated_at timestamp.
+ # Thus it will automatically generate a new fragment
+ # when the event is updated because the key changes.
+ def reset_events_cache
+ Event.where(target_id: self.id, target_type: 'MergeRequest').
+ order('id DESC').limit(100).
+ update_all(updated_at: Time.now)
+ end
+
+ def merge_commit_message
+ message = "Merge branch '#{source_branch}' into '#{target_branch}'"
+ message << "\n\n"
+ message << title.to_s
+ message << "\n\n"
+ message << description.to_s
+ message
+ end
+
+ # Return array of possible target branches
+ # dependes on target project of MR
+ def target_branches
+ if target_project.nil?
+ []
+ else
+ target_project.repository.branch_names
+ end
+ end
+
+ # Return array of possible source branches
+ # dependes on source project of MR
+ def source_branches
+ if source_project.nil?
+ []
+ else
+ source_project.repository.branch_names
+ end
end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
new file mode 100644
index 0000000000..7dce71a677
--- /dev/null
+++ b/app/models/merge_request_diff.rb
@@ -0,0 +1,180 @@
+# == Schema Information
+#
+# Table name: merge_request_diffs
+#
+# id :integer not null, primary key
+# state :string(255) default("collected"), not null
+# st_commits :text
+# st_diffs :text
+# merge_request_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+require Rails.root.join("app/models/commit")
+
+class MergeRequestDiff < ActiveRecord::Base
+ # Prevent store of diff
+ # if commits amount more then 200
+ COMMITS_SAFE_SIZE = 200
+
+ attr_reader :commits, :diffs
+
+ belongs_to :merge_request
+
+ attr_accessible :state, :st_commits, :st_diffs
+
+ delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
+
+ state_machine :state, initial: :empty do
+ state :collected
+ state :timeout
+ state :overflow_commits_safe_size
+ state :overflow_diff_files_limit
+ state :overflow_diff_lines_limit
+ end
+
+ serialize :st_commits
+ serialize :st_diffs
+
+ after_create :reload_content
+
+ def reload_content
+ reload_commits
+ reload_diffs
+ end
+
+ def diffs
+ @diffs ||= (load_diffs(st_diffs) || [])
+ end
+
+ def commits
+ @commits ||= load_commits(st_commits || [])
+ end
+
+ def last_commit
+ commits.first
+ end
+
+ def last_commit_short_sha
+ @last_commit_short_sha ||= last_commit.sha[0..10]
+ end
+
+ private
+
+ def dump_commits(commits)
+ commits.map(&:to_hash)
+ end
+
+ def load_commits(array)
+ array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) }
+ end
+
+ def dump_diffs(diffs)
+ if diffs.respond_to?(:map)
+ diffs.map(&:to_hash)
+ end
+ end
+
+ def load_diffs(raw)
+ if raw.respond_to?(:map)
+ raw.map { |hash| Gitlab::Git::Diff.new(hash) }
+ end
+ end
+
+ # Collect array of Git::Commit objects
+ # between target and source branches
+ def unmerged_commits
+ commits = if merge_request.for_fork?
+ compare_action.commits
+ else
+ repository.commits_between(target_branch, source_branch)
+ end
+
+ if commits.present?
+ commits = Commit.decorate(commits).
+ sort_by(&:created_at).
+ reverse
+ end
+
+ commits
+ end
+
+ # Reload all commits related to current merge request from repo
+ # and save it as array of hashes in st_commits db field
+ def reload_commits
+ commit_objects = unmerged_commits
+
+ if commit_objects.present?
+ self.st_commits = dump_commits(commit_objects)
+ end
+
+ save
+ end
+
+ # Reload diffs between branches related to current merge request from repo
+ # and save it as array of hashes in st_diffs db field
+ def reload_diffs
+ new_diffs = []
+
+ if commits.size.zero?
+ self.state = :empty
+ elsif commits.size > COMMITS_SAFE_SIZE
+ self.state = :overflow_commits_safe_size
+ else
+ new_diffs = unmerged_diffs
+ end
+
+ if new_diffs.any?
+ if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
+ self.state = :overflow_diff_files_limit
+ new_diffs = []
+ end
+
+ if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
+ self.state = :overflow_diff_lines_limit
+ new_diffs = []
+ end
+ end
+
+ if new_diffs.present?
+ new_diffs = dump_commits(new_diffs)
+ self.state = :collected
+ end
+
+ self.st_diffs = new_diffs
+ self.save
+ end
+
+ # Collect array of Git::Diff objects
+ # between target and source branches
+ def unmerged_diffs
+ diffs = if merge_request.for_fork?
+ compare_action.diffs
+ else
+ Gitlab::Git::Diff.between(repository, source_branch, target_branch)
+ end
+
+ diffs ||= []
+ diffs
+ rescue Gitlab::Git::Diff::TimeoutError => ex
+ self.state = :timeout
+ diffs = []
+ end
+
+ def repository
+ merge_request.target_project.repository
+ end
+
+ private
+
+ def compare_action
+ Gitlab::Satellite::CompareAction.new(
+ merge_request.author,
+ merge_request.target_project,
+ merge_request.target_branch,
+ merge_request.source_project,
+ merge_request.source_branch
+ )
+ end
+end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 1a73fa71e4..39ab0b536a 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -7,8 +7,8 @@
# project_id :integer not null
# description :text
# due_date :date
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# state :string(255)
# iid :integer
#
@@ -16,8 +16,7 @@
class Milestone < ActiveRecord::Base
include InternalId
- attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
- attr_accessor :author_id_of_changes
+ attr_accessible :title, :description, :due_date, :state_event
belongs_to :project
has_many :issues
@@ -26,6 +25,7 @@ class Milestone < ActiveRecord::Base
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
+ scope :of_projects, ->(ids) { where(project_id: ids) }
validates :title, presence: true
validates :project, presence: true
@@ -89,6 +89,6 @@ class Milestone < ActiveRecord::Base
end
def author_id
- author_id_of_changes
+ nil
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index fde06649c7..7973eef7e1 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -6,10 +6,11 @@
# name :string(255) not null
# path :string(255) not null
# owner_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# type :string(255)
# description :string(255) default(""), not null
+# avatar :string(255)
#
class Namespace < ActiveRecord::Base
@@ -26,7 +27,7 @@ class Namespace < ActiveRecord::Base
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
validates :description, length: { within: 0..255 }
- validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
+ validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
@@ -46,6 +47,14 @@ class Namespace < ActiveRecord::Base
def self.global_id
'GLN'
end
+
+ def projects_accessible_to(user)
+ projects.accessible_to(user)
+ end
+
+ def has_projects_accessible_to?(user)
+ projects_accessible_to(user).present?
+ end
def to_param
path
@@ -87,4 +96,8 @@ class Namespace < ActiveRecord::Base
def send_update_instructions
projects.each(&:send_move_instructions)
end
+
+ def kind
+ type == 'Group' ? 'group' : 'user'
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 7e7387abed..cee10ec90d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -6,15 +6,15 @@
# note :text
# noteable_type :string(255)
# author_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# project_id :integer
# attachment :string(255)
# line_code :string(255)
# commit_id :string(255)
# noteable_id :integer
-# st_diff :text
# system :boolean default(FALSE), not null
+# st_diff :text
#
require 'carrierwave/orm/activerecord'
@@ -23,6 +23,8 @@ require 'file_size_validator'
class Note < ActiveRecord::Base
include Mentionable
+ default_value_for :system, false
+
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id
attr_mentionable :note
@@ -56,29 +58,70 @@ class Note < ActiveRecord::Base
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
- def self.create_status_change_note(noteable, project, author, status, source)
- body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_"
+ class << self
+ def create_status_change_note(noteable, project, author, status, source)
+ body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_"
- create({
- noteable: noteable,
- project: project,
- author: author,
- note: body,
- system: true
- }, without_protection: true)
- end
+ create({
+ noteable: noteable,
+ project: project,
+ author: author,
+ note: body,
+ system: true
+ }, without_protection: true)
+ end
- # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
- # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
- def self.create_cross_reference_note(noteable, mentioner, author, project)
- create({
- noteable: noteable,
- commit_id: (noteable.sha if noteable.respond_to? :sha),
- project: project,
- author: author,
- note: "_mentioned in #{mentioner.gfm_reference}_",
- system: true
- }, without_protection: true)
+ # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
+ # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
+ def create_cross_reference_note(noteable, mentioner, author, project)
+ note_options = {
+ project: project,
+ author: author,
+ note: "_mentioned in #{mentioner.gfm_reference}_",
+ system: true
+ }
+
+ if noteable.kind_of?(Commit)
+ note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
+ else
+ note_options.merge!(noteable: noteable)
+ end
+
+ create(note_options, without_protection: true)
+ end
+
+ def create_assignee_change_note(noteable, project, author, assignee)
+ body = assignee.nil? ? '_Assignee removed_' : "_Reassigned to @#{assignee.username}_"
+
+ create({
+ noteable: noteable,
+ project: project,
+ author: author,
+ note: body,
+ system: true
+ }, without_protection: true)
+ end
+
+ def discussions_from_notes(notes)
+ discussion_ids = []
+ discussions = []
+
+ notes.each do |note|
+ next if discussion_ids.include?(note.discussion_id)
+
+ # don't group notes for the main target
+ if !note.for_diff_line? && note.noteable_type == "MergeRequest"
+ discussions << [note]
+ else
+ discussions << notes.select do |other_note|
+ note.discussion_id == other_note.discussion_id
+ end
+ discussion_ids << note.discussion_id
+ end
+ end
+
+ discussions
+ end
end
# Determine whether or not a cross-reference note already exists.
@@ -88,8 +131,8 @@ class Note < ActiveRecord::Base
def commit_author
@commit_author ||=
- project.users.find_by_email(noteable.author_email) ||
- project.users.find_by_name(noteable.author_name)
+ project.users.find_by(email: noteable.author_email) ||
+ project.users.find_by(name: noteable.author_name)
rescue
nil
end
@@ -141,9 +184,10 @@ class Note < ActiveRecord::Base
return @diff_line if @diff_line
if diff
- Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|
- @diff_line = full_line if line_code == self.line_code
- end
+ Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
+ .each do |full_line, type, line_code, line_new, line_old|
+ @diff_line = full_line if line_code == self.line_code
+ end
end
@diff_line
@@ -157,7 +201,9 @@ class Note < ActiveRecord::Base
# otherwise false is returned
def downvote?
votable? && (note.start_with?('-1') ||
- note.start_with?(':-1:')
+ note.start_with?(':-1:') ||
+ note.start_with?(':thumbsdown:') ||
+ note.start_with?(':thumbs_down_sign:')
)
end
@@ -206,7 +252,9 @@ class Note < ActiveRecord::Base
# otherwise false is returned
def upvote?
votable? && (note.start_with?('+1') ||
- note.start_with?(':+1:')
+ note.start_with?(':+1:') ||
+ note.start_with?(':thumbsup:') ||
+ note.start_with?(':thumbs_up_sign:')
)
end
@@ -237,4 +285,19 @@ class Note < ActiveRecord::Base
def noteable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
+
+ # Reset notes events cache
+ #
+ # Since we do cache @event we need to reset cache in special cases:
+ # * when a note is updated
+ # * when a note is removed
+ # Events cache stored like events/23-20130109142513.
+ # The cache key includes updated_at timestamp.
+ # Thus it will automatically generate a new fragment
+ # when the event is updated because the key changes.
+ def reset_events_cache
+ Event.where(target_id: self.id, target_type: 'Note').
+ order('id DESC').limit(100).
+ update_all(updated_at: Time.now)
+ end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index ff6a18d6a5..b0f8ed6a4e 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -9,12 +9,23 @@ class Notification
attr_accessor :target
- def self.notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH]
- end
+ class << self
+ def notification_levels
+ [N_DISABLED, N_PARTICIPATING, N_WATCH]
+ end
- def self.project_notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ def options_with_labels
+ {
+ disabled: N_DISABLED,
+ participating: N_PARTICIPATING,
+ watch: N_WATCH,
+ global: N_GLOBAL
+ }
+ end
+
+ def project_notification_levels
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ end
end
def initialize(target)
@@ -36,4 +47,8 @@ class Notification
def global?
target.notification_level == N_GLOBAL
end
+
+ def level
+ target.notification_level
+ end
end
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index ef2000ad05..a3c0d201ee 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
-# content :text(2147483647)
+# content :text
# author_id :integer not null
# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
diff --git a/app/models/project.rb b/app/models/project.rb
index 6b8e54d5b2..45e950f480 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -6,64 +6,79 @@
# name :string(255)
# path :string(255)
# description :text
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# creator_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
# namespace_id :integer
-# public :boolean default(FALSE), not null
# issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
-# imported :boolean default(FALSE), not null
# import_url :string(255)
+# visibility_level :integer default(0), not null
+# archived :boolean default(FALSE), not null
+# import_status :string(255)
#
class Project < ActiveRecord::Base
include Gitlab::ShellAdapter
+ include Gitlab::VisibilityLevel
extend Enumerize
- attr_accessible :name, :path, :description, :default_branch, :issues_tracker, :label_list,
+ default_value_for :archived, false
+ default_value_for :issues_enabled, true
+ default_value_for :wall_enabled, true
+ default_value_for :merge_requests_enabled, true
+ default_value_for :wiki_enabled, true
+ default_value_for :snippets_enabled, true
+
+ ActsAsTaggableOn.strict_case_match = true
+
+ attr_accessible :name, :path, :description, :issues_tracker, :label_list,
:issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
- :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin]
+ :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin]
attr_accessible :namespace_id, :creator_id, as: :admin
acts_as_taggable_on :labels, :issues_default_labels
+ attr_accessor :new_default_branch
+
# Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
- belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
+ belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id"
belongs_to :namespace
- has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
+ has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
+ has_one :emails_on_push_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy
+ has_one :assembla_service, dependent: :destroy
+ has_one :gemnasium_service, dependent: :destroy
+ has_one :slack_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
-
+ # Merge Requests for target project should be removed with it
+ has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
+ # Merge requests from source project should be kept when source project was removed
+ has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
+ has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
- has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
- has_many :fork_merge_requests,dependent: :destroy, foreign_key: "source_project_id", class_name: MergeRequest
- has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :protected_branches, dependent: :destroy
-
has_many :users_projects, dependent: :destroy
has_many :users, through: :users_projects
-
has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects
@@ -71,8 +86,8 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true
# Validations
- validates :creator, presence: true
- validates :description, length: { within: 0..2000 }
+ validates :creator, presence: true, on: :create
+ validates :description, length: { maximum: 2000 }, allow_blank: true
validates :name, presence: true, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first" }
@@ -82,16 +97,13 @@ class Project < ActiveRecord::Base
message: "only letters, digits & '_' '-' '.' allowed. Letter or digit should be first" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
- validates :issues_tracker_id, length: { within: 0..255 }
-
+ validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
-
validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import?
-
validate :check_limit, on: :create
# Scopes
@@ -104,15 +116,57 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
- scope :public_only, -> { where(public: true) }
+ scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
+ scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
+ scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
+ state_machine :import_status, initial: :none do
+ event :import_start do
+ transition :none => :started
+ end
+
+ event :import_finish do
+ transition :started => :finished
+ end
+
+ event :import_fail do
+ transition :started => :failed
+ end
+
+ event :import_retry do
+ transition :failed => :started
+ end
+
+ state :started
+ state :finished
+ state :failed
+
+ after_transition any => :started, :do => :add_import_job
+ end
+
class << self
+ def public_and_internal_levels
+ [Project::PUBLIC, Project::INTERNAL]
+ end
+
def abandoned
where('projects.last_activity_at < ?', 6.months.ago)
end
+ def publicish(user)
+ visibility_levels = [Project::PUBLIC]
+ visibility_levels += [Project::INTERNAL] if user
+ where(visibility_level: visibility_levels)
+ end
+
+ def accessible_to(user)
+ accessible_ids = publicish(user).pluck(:id)
+ accessible_ids += user.authorized_projects.pluck(:id) if user
+ where(id: accessible_ids)
+ end
+
def with_push
includes(:events).where('events.action = ?', Event::PUSHED)
end
@@ -122,18 +176,35 @@ class Project < ActiveRecord::Base
end
def search query
- joins(:namespace).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query", query: "%#{query}%")
+ joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
+ end
+
+ def search_by_title query
+ where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
end
def find_with_namespace(id)
- if id.include?("/")
- id = id.split("/")
- namespace = Namespace.find_by_path(id.first)
- return nil unless namespace
+ return nil unless id.include?("/")
- where(namespace_id: namespace.id).find_by_path(id.second)
- else
- where(path: id, namespace_id: nil).last
+ id = id.split("/")
+ namespace = Namespace.find_by(path: id.first)
+ return nil unless namespace
+
+ where(namespace_id: namespace.id).find_by(path: id.second)
+ end
+
+ def visibility_levels
+ Gitlab::VisibilityLevel.options
+ end
+
+ def sort(method)
+ case method.to_s
+ when 'newest' then reorder('projects.created_at DESC')
+ when 'oldest' then reorder('projects.created_at ASC')
+ when 'recently_updated' then reorder('projects.updated_at DESC')
+ when 'last_updated' then reorder('projects.updated_at ASC')
+ when 'largest_repository' then reorder('projects.repository_size DESC')
+ else reorder("namespaces.path, projects.name ASC")
end
end
end
@@ -143,24 +214,40 @@ class Project < ActiveRecord::Base
end
def repository
- @repository ||= Repository.new(path_with_namespace, default_branch)
+ @repository ||= Repository.new(path_with_namespace)
end
def saved?
id && persisted?
end
+ def add_import_job
+ RepositoryImportWorker.perform_in(2.seconds, id)
+ end
+
def import?
import_url.present?
end
def imported?
- imported
+ import_finished?
+ end
+
+ def import_in_progress?
+ import? && import_status == 'started'
+ end
+
+ def import_failed?
+ import_status == 'failed'
+ end
+
+ def import_finished?
+ import_status == 'finished'
end
def check_limit
unless creator.can_create_project?
- errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
+ errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
end
rescue
errors[:base] << ("Can't check your ability to create project")
@@ -174,6 +261,10 @@ class Project < ActiveRecord::Base
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
end
+ def web_url_without_protocol
+ web_url.split("://")[1]
+ end
+
def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: "Commit")
end
@@ -190,8 +281,11 @@ class Project < ActiveRecord::Base
self.id
end
+ # Tags are shared by issues and merge requests
def issues_labels
- @issues_labels ||= (issues_default_labels + issues.tags_on(:labels)).uniq.sort_by(&:name)
+ @issues_labels ||= (issues_default_labels +
+ merge_requests.tags_on(:labels) +
+ issues.tags_on(:labels)).uniq.sort_by(&:name)
end
def issue_exists?(issue_id)
@@ -221,7 +315,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack)
end
def gitlab_ci?
@@ -243,9 +337,7 @@ class Project < ActiveRecord::Base
end
def send_move_instructions
- team.members.each do |user|
- Notify.delay.project_was_moved_email(self.id, user.id)
- end
+ NotificationService.new.project_was_moved(self)
end
def owner
@@ -263,7 +355,7 @@ class Project < ActiveRecord::Base
# Get Team Member record by user id
def team_member_by_id(user_id)
- users_projects.find_by_user_id(user_id)
+ users_projects.find_by(user_id: user_id)
end
def name_with_namespace
@@ -288,8 +380,10 @@ class Project < ActiveRecord::Base
ProjectTransferService.new.transfer(self, new_namespace)
end
- def execute_hooks(data)
- hooks.each { |hook| hook.async_execute(data) }
+ def execute_hooks(data, hooks_scope = :push_hooks)
+ hooks.send(hooks_scope).each do |hook|
+ hook.async_execute(data)
+ end
end
def execute_services(data)
@@ -300,30 +394,21 @@ class Project < ActiveRecord::Base
end
end
- def discover_default_branch
- # Discover the default branch, but only if it hasn't already been set to
- # something else
- if repository.exists? && default_branch.nil?
- update_attributes(default_branch: self.repository.discover_default_branch)
- end
- end
-
def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/
branch_name = ref.gsub("refs/heads/", "")
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
- # Update code for merge requests into project between project branches
- mrs = self.merge_requests.opened.by_branch(branch_name).all
- # Update code for merge requests between project and project fork
- mrs += self.fork_merge_requests.opened.by_branch(branch_name).all
-
- mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
-
# Close merge requests
- mrs = self.merge_requests.opened.where(target_branch: branch_name).all
+ mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
- mrs.each { |merge_request| merge_request.merge!(user.id) }
+ mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
+
+ # Update code for merge requests into project between project branches
+ mrs = self.merge_requests.opened.by_branch(branch_name).to_a
+ # Update code for merge requests between project and project fork
+ mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
+ mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
true
end
@@ -390,7 +475,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
- http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
+ [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Check if current branch name is marked as protected in the system
@@ -447,4 +532,42 @@ class Project < ActiveRecord::Base
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
+
+ def project_member(user)
+ users_projects.where(user_id: user).first
+ end
+
+ def default_branch
+ @default_branch ||= repository.root_ref if repository.exists?
+ end
+
+ def reload_default_branch
+ @default_branch = nil
+ default_branch
+ end
+
+ def visibility_level_field
+ visibility_level
+ end
+
+ def archive!
+ update_attribute(:archived, true)
+ end
+
+ def unarchive!
+ update_attribute(:archived, false)
+ end
+
+ def change_head(branch)
+ gitlab_shell.update_repository_head(self.path_with_namespace, branch)
+ reload_default_branch
+ end
+
+ def forked_from?(project)
+ forked? && project == forked_from_project
+ end
+
+ def update_repository_size
+ update_attribute(:repository_size, repository.size)
+ end
end
diff --git a/app/models/project_hook.rb b/app/models/project_hook.rb
index 2576fc979d..6db6767a88 100644
--- a/app/models/project_hook.rb
+++ b/app/models/project_hook.rb
@@ -2,15 +2,26 @@
#
# Table name: web_hooks
#
-# id :integer not null, primary key
-# url :string(255)
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255) default("ProjectHook")
-# service_id :integer
+# id :integer not null, primary key
+# url :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string(255) default("ProjectHook")
+# service_id :integer
+# push_events :boolean default(TRUE), not null
+# issues_events :boolean default(FALSE), not null
+# merge_requests_events :boolean default(FALSE), not null
+# tag_push_events :boolean default(FALSE)
#
class ProjectHook < WebHook
belongs_to :project
+
+ attr_accessible :push_events, :issues_events, :merge_requests_events, :tag_push_events
+
+ scope :push_hooks, -> { where(push_events: true) }
+ scope :tag_push_hooks, -> { where(tag_push_events: true) }
+ scope :issue_hooks, -> { where(issues_events: true) }
+ scope :merge_request_hooks, -> { where(merge_requests_events: true) }
end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
new file mode 100644
index 0000000000..06e9d6118d
--- /dev/null
+++ b/app/models/project_services/assembla_service.rb
@@ -0,0 +1,50 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# recipients :text
+# api_key :string(255)
+#
+
+class AssemblaService < Service
+ attr_accessible :subdomain
+
+ include HTTParty
+
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Assembla'
+ end
+
+ def description
+ 'Project Management Software (Source Commits Endpoint)'
+ end
+
+ def to_param
+ 'assembla'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'subdomain', placeholder: '' }
+ ]
+ end
+
+ def execute(push)
+ url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}"
+ AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
+ end
+end
diff --git a/app/models/campfire_service.rb b/app/models/project_services/campfire_service.rb
similarity index 94%
rename from app/models/campfire_service.rb
rename to app/models/project_services/campfire_service.rb
index fb2a49fd58..19030ecffa 100644
--- a/app/models/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -7,12 +7,14 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# recipients :text
+# api_key :string(255)
#
class CampfireService < Service
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
new file mode 100644
index 0000000000..04775c4f2b
--- /dev/null
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -0,0 +1,46 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# recipients :text
+# api_key :string(255)
+#
+
+class EmailsOnPushService < Service
+ attr_accessible :recipients
+
+ validates :recipients, presence: true, if: :activated?
+
+ def title
+ 'Emails on push'
+ end
+
+ def description
+ 'Email the commits and diff of each push to a list of recipients.'
+ end
+
+ def to_param
+ 'emails_on_push'
+ end
+
+ def execute(push_data)
+ EmailsOnPushWorker.perform_async(project_id, recipients, push_data)
+ end
+
+ def fields
+ [
+ { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
+ ]
+ end
+end
diff --git a/app/models/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
similarity index 88%
rename from app/models/flowdock_service.rb
rename to app/models/project_services/flowdock_service.rb
index 6ec431d4a1..6cdd04a864 100644
--- a/app/models/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -7,10 +7,14 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# recipients :text
+# api_key :string(255)
#
require "flowdock-git-hook"
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
new file mode 100644
index 0000000000..b363d7f57d
--- /dev/null
+++ b/app/models/project_services/gemnasium_service.rb
@@ -0,0 +1,55 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# recipients :text
+# api_key :string(255)
+#
+
+require "gemnasium/gitlab_service"
+
+class GemnasiumService < Service
+ validates :token, :api_key, presence: true, if: :activated?
+
+ def title
+ 'Gemnasium'
+ end
+
+ def description
+ 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
+ end
+
+ def to_param
+ 'gemnasium'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' },
+ { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' }
+ ]
+ end
+
+ def execute(push_data)
+ repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ Gemnasium::GitlabService.execute(
+ ref: push_data[:ref],
+ before: push_data[:before],
+ after: push_data[:after],
+ token: token,
+ api_key: api_key,
+ repo: repo_path
+ )
+ end
+end
diff --git a/app/models/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
similarity index 90%
rename from app/models/gitlab_ci_service.rb
rename to app/models/project_services/gitlab_ci_service.rb
index 7f5380a455..ed1944f337 100644
--- a/app/models/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -7,12 +7,14 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# recipients :text
+# api_key :string(255)
#
class GitlabCiService < Service
@@ -36,7 +38,7 @@ class GitlabCiService < Service
end
def commit_status sha
- response = HTTParty.get(commit_status_path(sha))
+ response = HTTParty.get(commit_status_path(sha), verify: false)
if response.code == 200 and response["status"]
response["status"]
diff --git a/app/models/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
similarity index 86%
rename from app/models/hipchat_service.rb
rename to app/models/project_services/hipchat_service.rb
index 7fec5c4fbe..d62f61856d 100644
--- a/app/models/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -7,12 +7,14 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# recipients :text
+# api_key :string(255)
#
class HipchatService < Service
@@ -25,7 +27,7 @@ class HipchatService < Service
end
def description
- 'Simple web-based real-time group chat'
+ 'Private group chat and IM'
end
def to_param
@@ -40,7 +42,7 @@ class HipchatService < Service
end
def execute(push_data)
- gate[room].send('Gitlab', create_message(push_data))
+ gate[room].send('GitLab', create_message(push_data))
end
private
@@ -61,7 +63,7 @@ class HipchatService < Service
elsif after =~ /000000/
message << "removed branch #{ref} from #{project.name_with_namespace.gsub!(/\s/,'')} \n"
else
- message << "#pushed to branch #{ref} "
+ message << "pushed to branch #{ref} "
message << "of #{project.name_with_namespace.gsub!(/\s/,'')} "
message << "(Compare changes)"
for commit in push[:commits] do
diff --git a/app/models/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
similarity index 92%
rename from app/models/pivotaltracker_service.rb
rename to app/models/project_services/pivotaltracker_service.rb
index c5b1b9ab8d..aa2bcc5def 100644
--- a/app/models/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -7,12 +7,14 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# recipients :text
+# api_key :string(255)
#
class PivotaltrackerService < Service
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
new file mode 100644
index 0000000000..28204e5ea6
--- /dev/null
+++ b/app/models/project_services/slack_message.rb
@@ -0,0 +1,110 @@
+require 'slack-notifier'
+
+class SlackMessage
+ def initialize(params)
+ @after = params.fetch(:after)
+ @before = params.fetch(:before)
+ @commits = params.fetch(:commits, [])
+ @project_name = params.fetch(:project_name)
+ @project_url = params.fetch(:project_url)
+ @ref = params.fetch(:ref).gsub('refs/heads/', '')
+ @username = params.fetch(:user_name)
+ end
+
+ def pretext
+ format(message)
+ end
+
+ def attachments
+ return [] if new_branch? || removed_branch?
+
+ commit_message_attachments
+ end
+
+ private
+
+ attr_reader :after
+ attr_reader :before
+ attr_reader :commits
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :ref
+ attr_reader :username
+
+ def message
+ if new_branch?
+ new_branch_message
+ elsif removed_branch?
+ removed_branch_message
+ else
+ push_message
+ end
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def new_branch_message
+ "#{username} pushed new branch #{branch_link} to #{project_link}"
+ end
+
+ def removed_branch_message
+ "#{username} removed branch #{ref} from #{project_link}"
+ end
+
+ def push_message
+ "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})"
+ end
+
+ def commit_messages
+ commits.each_with_object('') do |commit, str|
+ str << compose_commit_message(commit)
+ end.chomp
+ end
+
+ def commit_message_attachments
+ [{ text: format(commit_messages), color: attachment_color }]
+ end
+
+ def compose_commit_message(commit)
+ author = commit.fetch(:author).fetch(:name)
+ id = commit.fetch(:id)[0..8]
+ message = commit.fetch(:message)
+ url = commit.fetch(:url)
+
+ "[#{id}](#{url}): #{message} - #{author}\n"
+ end
+
+ def new_branch?
+ before =~ /000000/
+ end
+
+ def removed_branch?
+ after =~ /000000/
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def compare_url
+ "#{project_url}/compare/#{before}...#{after}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def compare_link
+ "[Compare changes](#{compare_url})"
+ end
+
+ def attachment_color
+ '#345'
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
new file mode 100644
index 0000000000..50fd62def1
--- /dev/null
+++ b/app/models/project_services/slack_service.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# recipients :text
+# api_key :string(255)
+#
+
+class SlackService < Service
+ attr_accessible :room
+ attr_accessible :subdomain
+
+ validates :room, presence: true, if: :activated?
+ validates :subdomain, presence: true, if: :activated?
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Slack'
+ end
+
+ def description
+ 'A team communication tool for the 21st century'
+ end
+
+ def to_param
+ 'slack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'subdomain', placeholder: '' },
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'room', placeholder: 'Ex. #general' },
+ ]
+ end
+
+ def execute(push_data)
+ message = SlackMessage.new(push_data.merge(
+ project_url: project_url,
+ project_name: project_name
+ ))
+
+ notifier = Slack::Notifier.new(subdomain, token)
+ notifier.channel = room
+ notifier.username = 'GitLab'
+ notifier.ping(message.pretext, attachments: message.attachments)
+ end
+
+ private
+
+ def project_name
+ project.name_with_namespace.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index f38aa07059..14c8804642 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
-# content :text(2147483647)
+# content :text
# author_id :integer not null
# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index bc35c4041b..eca13e5606 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -22,22 +22,22 @@ class ProjectTeam
end
def find(user_id)
- user = project.users.find_by_id(user_id)
+ user = project.users.find_by(id: user_id)
if group
- user ||= group.users.find_by_id(user_id)
+ user ||= group.users.find_by(id: user_id)
end
user
end
def find_tm(user_id)
- tm = project.users_projects.find_by_user_id(user_id)
+ tm = project.users_projects.find_by(user_id: user_id)
# If user is not in project members
# we should check for group membership
if group && !tm
- tm = group.users_groups.find_by_user_id(user_id)
+ tm = group.users_groups.find_by(user_id: user_id)
end
tm
@@ -64,6 +64,10 @@ class ProjectTeam
UsersProject.truncate_team(project)
end
+ def users
+ members
+ end
+
def members
@members ||= fetch_members
end
@@ -87,9 +91,8 @@ class ProjectTeam
def import(source_project)
target_project = project
- source_team = source_project.users_projects.all
- target_team = target_project.users_projects.all
- target_user_ids = target_team.map(&:user_id)
+ source_team = source_project.users_projects.to_a
+ target_user_ids = target_project.users_projects.pluck(:user_id)
source_team.reject! do |tm|
# Skip if user already present in team
diff --git a/app/models/gollum_wiki.rb b/app/models/project_wiki.rb
similarity index 85%
rename from app/models/gollum_wiki.rb
rename to app/models/project_wiki.rb
index 05fc2a745b..08a5278247 100644
--- a/app/models/gollum_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -1,4 +1,5 @@
-class GollumWiki
+class ProjectWiki
+ include Gitlab::ShellAdapter
MARKUPS = {
"Markdown" => :markdown,
@@ -33,14 +34,14 @@ class GollumWiki
end
def http_url_to_repo
- http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
+ [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Returns the Gollum::Wiki object.
def wiki
@wiki ||= begin
Gollum::Wiki.new(path_to_repo)
- rescue Grit::NoSuchPathError
+ rescue Gollum::NoSuchPathError
create_repo!
end
end
@@ -63,7 +64,8 @@ class GollumWiki
#
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil)
- if page = wiki.page(title, version)
+ page_title, page_dir = page_title_and_dir(title)
+ if page = wiki.page(page_title, version, page_dir)
WikiPage.new(self, page, true)
else
nil
@@ -89,6 +91,12 @@ class GollumWiki
wiki.delete_page(page, commit_details(:deleted, message, page.title))
end
+ def page_title_and_dir(title)
+ title_array = title.split("/")
+ title = title_array.pop
+ [title.gsub(/\.[^.]*$/, ""), title_array.join("/")]
+ end
+
private
def create_repo!
@@ -113,10 +121,6 @@ class GollumWiki
"#{@user.username} #{action} page: #{title}"
end
- def gitlab_shell
- @gitlab_shell ||= Gitlab::Shell.new
- end
-
def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 16379720e5..d2b2b1218d 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -5,8 +5,8 @@
# id :integer not null, primary key
# project_id :integer not null
# name :string(255) not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
#
class ProtectedBranch < ActiveRecord::Base
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 97b4330092..eadc34127c 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -3,7 +3,7 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace
- def initialize(path_with_namespace, default_branch)
+ def initialize(path_with_namespace, default_branch = nil)
@path_with_namespace = path_with_namespace
@raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace
rescue Gitlab::Git::Repository::NoRepository
@@ -57,7 +57,7 @@ class Repository
def recent_branches(limit = 20)
branches.sort do |a, b|
- a.commit.committed_date <=> b.commit.committed_date
+ commit(b.target).committed_date <=> commit(a.target).committed_date
end[0..limit]
end
@@ -133,6 +133,8 @@ class Repository
Rails.cache.delete(cache_key(:tag_names))
Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log))
+ Rails.cache.delete(cache_key(:readme))
+ Rails.cache.delete(cache_key(:contribution_guide))
end
def graph_log
@@ -159,4 +161,63 @@ class Repository
def blob_at(sha, path)
Gitlab::Git::Blob.find(self, sha, path)
end
+
+ def readme
+ Rails.cache.fetch(cache_key(:readme)) do
+ tree(:head).readme
+ end
+ end
+
+ def contribution_guide
+ Rails.cache.fetch(cache_key(:contribution_guide)) do
+ tree(:head).contribution_guide
+ end
+ end
+
+ def head_commit
+ commit(self.root_ref)
+ end
+
+ def tree(sha = :head, path = nil)
+ if sha == :head
+ sha = head_commit.sha
+ end
+
+ Tree.new(self, sha, path)
+ end
+
+ def blob_at_branch(branch_name, path)
+ last_commit = commit(branch_name)
+
+ if last_commit
+ blob_at(last_commit.sha, path)
+ else
+ nil
+ end
+ end
+
+ # Returns url for submodule
+ #
+ # Ex.
+ # @repository.submodule_url_for('master', 'rack')
+ # # => git@localhost:rack.git
+ #
+ def submodule_url_for(ref, path)
+ if submodules(ref).any?
+ submodule = submodules(ref)[path]
+
+ if submodule
+ submodule['url']
+ end
+ end
+ end
+
+ def last_commit_for_path(sha, path)
+ commits(sha, path, 1).last
+ end
+
+ # Remove archives older than 2 hours
+ def clean_old_archives
+ Gitlab::Popen.popen(%W(find #{Gitlab.config.gitlab.repository_downloads_path} -mmin +120 -delete))
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 540aaad1ce..ea00039521 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -7,18 +7,22 @@
# title :string(255)
# token :string(255)
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
+# recipients :text
+# api_key :string(255)
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
- attr_accessible :title, :token, :type, :active
+ default_value_for :active, false
+
+ attr_accessible :title, :token, :type, :active, :api_key
belongs_to :project
has_one :service_hook
diff --git a/app/models/service_hook.rb b/app/models/service_hook.rb
index 4cd2b272ee..2e11239c40 100644
--- a/app/models/service_hook.rb
+++ b/app/models/service_hook.rb
@@ -2,13 +2,17 @@
#
# Table name: web_hooks
#
-# id :integer not null, primary key
-# url :string(255)
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255) default("ProjectHook")
-# service_id :integer
+# id :integer not null, primary key
+# url :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string(255) default("ProjectHook")
+# service_id :integer
+# push_events :boolean default(TRUE), not null
+# issues_events :boolean default(FALSE), not null
+# merge_requests_events :boolean default(FALSE), not null
+# tag_push_events :boolean default(FALSE)
#
class ServiceHook < WebHook
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index edc179b20f..720accd73d 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# title :string(255)
-# content :text(2147483647)
+# content :text
# author_id :integer not null
# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
@@ -20,6 +20,8 @@ class Snippet < ActiveRecord::Base
attr_accessible :title, :content, :file_name, :expires_at, :private
+ default_value_for :private, true
+
belongs_to :author, class_name: "User"
has_many :notes, as: :noteable, dependent: :destroy
diff --git a/app/models/system_hook.rb b/app/models/system_hook.rb
index 5cdf046644..ee32b49bc6 100644
--- a/app/models/system_hook.rb
+++ b/app/models/system_hook.rb
@@ -2,13 +2,17 @@
#
# Table name: web_hooks
#
-# id :integer not null, primary key
-# url :string(255)
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255) default("ProjectHook")
-# service_id :integer
+# id :integer not null, primary key
+# url :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string(255) default("ProjectHook")
+# service_id :integer
+# push_events :boolean default(TRUE), not null
+# issues_events :boolean default(FALSE), not null
+# merge_requests_events :boolean default(FALSE), not null
+# tag_push_events :boolean default(FALSE)
#
class SystemHook < WebHook
diff --git a/app/models/tree.rb b/app/models/tree.rb
index ed06cb1a12..ac2183be44 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -1,5 +1,5 @@
class Tree
- attr_accessor :entries, :readme
+ attr_accessor :entries, :readme, :contribution_guide
def initialize(repository, sha, path = '/')
path = '/' if path.blank?
@@ -10,6 +10,11 @@ class Tree
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
end
+
+ if contribution_tree = @entries.find(&:contributing?)
+ contribution_path = path == '/' ? contribution_tree.name : File.join(path, contribution_tree.name)
+ @contribution_guide = Gitlab::Git::Blob.find(git_repo, sha, contribution_path)
+ end
end
def trees
@@ -23,4 +28,8 @@ class Tree
def submodules
@entries.select(&:submodule?)
end
+
+ def sorted_entries
+ trees + blobs + submodules
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index df14cf34e8..16961e5413 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,52 +2,65 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime not null
-# updated_at :datetime not null
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# extern_uid :string(255)
-# provider :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# extern_uid :string(255)
+# provider :string(255)
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
+ default_value_for :admin, false
+ default_value_for :can_create_group, true
+ default_value_for :can_create_team, false
+ default_value_for :hide_no_ssh_key, false
+
devise :database_authenticatable, :token_authenticatable, :lockable, :async,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
- :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
- :extern_uid, :provider, :password_expires_at, :avatar,
+ :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
+ :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
as: [:default, :admin]
attr_accessible :projects_limit, :can_create_group,
@@ -67,55 +80,50 @@ class User < ActiveRecord::Base
#
# Namespace for personal projects
- has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
-
- # Namespaces (owned groups and own namespace)
- has_many :namespaces, foreign_key: :owner_id
+ has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace"
# Profile
has_many :keys, dependent: :destroy
+ has_many :emails, dependent: :destroy
# Groups
- has_many :own_groups, class_name: "Group", foreign_key: :owner_id
- has_many :owned_groups, through: :users_groups, source: :group, conditions: { users_groups: { group_access: UsersGroup::OWNER } }
-
has_many :users_groups, dependent: :destroy
has_many :groups, through: :users_groups
-
+ has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group
# Projects
+ has_many :groups_projects, through: :groups, source: :projects
+ has_many :personal_projects, through: :namespace, source: :projects
+ has_many :projects, through: :users_projects
+ has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
+
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
- has_many :recent_events, foreign_key: :author_id, class_name: "Event", order: "id DESC"
+ has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
- has_many :groups_projects, through: :groups, source: :projects
- has_many :personal_projects, through: :namespace, source: :projects
- has_many :projects, through: :users_projects
- has_many :own_projects, foreign_key: :creator_id, class_name: 'Project'
- has_many :owned_projects, through: :namespaces, source: :projects
#
# Validations
#
validates :name, presence: true
- validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
- validates :bio, length: { within: 0..255 }
+ validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
+ validates :bio, length: { maximum: 255 }, allow_blank: true
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,
+ validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.username_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
-
validate :namespace_uniq, if: ->(user) { user.username_changed? }
-
+ validate :avatar_type, if: ->(user) { user.avatar_changed? }
+ validate :unique_email, if: ->(user) { user.email_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
before_validation :generate_password, on: :create
@@ -164,7 +172,7 @@ class User < ActiveRecord::Base
scope :alphabetically, -> { order('name ASC') }
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
- scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped }
+ scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
scope :ldap, -> { where(provider: 'ldap') }
@@ -184,6 +192,13 @@ class User < ActiveRecord::Base
end
end
+ def find_for_commit(email, name)
+ # Prefer email match over name match
+ User.where(email: email).first ||
+ User.joins(:emails).where(emails: { email: email }).first ||
+ User.where(name: name).first
+ end
+
def filter filter_name
case filter_name
when "admins"; self.admins
@@ -195,11 +210,11 @@ class User < ActiveRecord::Base
end
def search query
- where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
+ where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
def by_username_or_id(name_or_id)
- where('username = ? OR id = ?', name_or_id, name_or_id).first
+ where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
def build_user(attrs = {}, options= {})
@@ -239,15 +254,25 @@ class User < ActiveRecord::Base
def namespace_uniq
namespace_name = self.username
- if Namespace.find_by_path(namespace_name)
- self.errors.add :username, "already exist"
+ if Namespace.find_by(path: namespace_name)
+ self.errors.add :username, "already exists"
end
end
+ def avatar_type
+ unless self.avatar.image?
+ self.errors.add :avatar, "only images allowed"
+ end
+ end
+
+ def unique_email
+ self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email)
+ end
+
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
- group_ids = (groups.pluck(:id) + own_groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
+ group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
Group.where(id: group_ids).order('namespaces.name ASC')
end
end
@@ -256,11 +281,19 @@ class User < ActiveRecord::Base
# Projects user has access to
def authorized_projects
@authorized_projects ||= begin
- project_ids = (owned_projects.pluck(:id) + groups_projects.pluck(:id) + projects.pluck(:id)).uniq
+ project_ids = personal_projects.pluck(:id)
+ project_ids += groups_projects.pluck(:id)
+ project_ids += projects.pluck(:id).uniq
Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
end
end
+ def owned_projects
+ @owned_projects ||= begin
+ Project.where(namespace_id: owned_groups.pluck(:id).push(namespace.id)).joins(:namespace)
+ end
+ end
+
# Team membership in authorized projects
def tm_in_authorized_projects
UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id)
@@ -333,7 +366,7 @@ class User < ActiveRecord::Base
end
def several_namespaces?
- namespaces.many? || owned_groups.any?
+ owned_groups.any?
end
def namespace_id
@@ -367,11 +400,11 @@ class User < ActiveRecord::Base
end
def accessible_deploy_keys
- DeployKey.in_projects(self.authorized_projects).uniq
+ DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq
end
def created_by
- User.find_by_id(created_by_id) if created_by_id
+ User.find_by(id: created_by_id) if created_by_id
end
def sanitize_attrs
@@ -381,6 +414,14 @@ class User < ActiveRecord::Base
end
end
+ def requires_ldap_check?
+ if ldap_user?
+ !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
+ else
+ false
+ end
+ end
+
def solo_owned_groups
@solo_owned_groups ||= owned_groups.select do |group|
group.owners == [self]
@@ -394,4 +435,45 @@ class User < ActiveRecord::Base
self
end
+
+ def can_leave_project?(project)
+ project.namespace != namespace &&
+ project.project_member(self)
+ end
+
+ # Reset project events cache related to this user
+ #
+ # Since we do cache @event we need to reset cache in special cases:
+ # * when the user changes their avatar
+ # Events cache stored like events/23-20130109142513.
+ # The cache key includes updated_at timestamp.
+ # Thus it will automatically generate a new fragment
+ # when the event is updated because the key changes.
+ def reset_events_cache
+ Event.where(author_id: self.id).
+ order('id DESC').limit(1000).
+ update_all(updated_at: Time.now)
+ end
+
+ def full_website_url
+ return "http://#{website_url}" if website_url !~ /^https?:\/\//
+
+ website_url
+ end
+
+ def short_website_url
+ website_url.gsub(/https?:\/\//, '')
+ end
+
+ def all_ssh_keys
+ keys.map(&:key)
+ end
+
+ def temp_oauth_email?
+ email =~ /\Atemp-email-for-oauth/
+ end
+
+ def generate_tmp_oauth_email
+ self.email = "temp-email-for-oauth-#{username}@gitlab.localhost"
+ end
end
diff --git a/app/models/users_group.rb b/app/models/users_group.rb
index 181bf32228..5f8b832d51 100644
--- a/app/models/users_group.rb
+++ b/app/models/users_group.rb
@@ -6,8 +6,8 @@
# group_access :integer not null
# group_id :integer not null
# user_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# notification_level :integer default(3), not null
#
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
index 6f147859a5..e8810f56de 100644
--- a/app/models/users_project.rb
+++ b/app/models/users_project.rb
@@ -5,8 +5,8 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# created_at :datetime
+# updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb
index 3f22b1082f..76854da5c3 100644
--- a/app/models/web_hook.rb
+++ b/app/models/web_hook.rb
@@ -2,18 +2,26 @@
#
# Table name: web_hooks
#
-# id :integer not null, primary key
-# url :string(255)
-# project_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255) default("ProjectHook")
-# service_id :integer
+# id :integer not null, primary key
+# url :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string(255) default("ProjectHook")
+# service_id :integer
+# push_events :boolean default(TRUE), not null
+# issues_events :boolean default(FALSE), not null
+# merge_requests_events :boolean default(FALSE), not null
+# tag_push_events :boolean default(FALSE)
#
class WebHook < ActiveRecord::Base
include HTTParty
+ default_value_for :push_events, true
+ default_value_for :issues_events, false
+ default_value_for :merge_requests_events, false
+
attr_accessible :url
# HTTParty timeout
@@ -25,7 +33,7 @@ class WebHook < ActiveRecord::Base
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" })
+ WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
@@ -35,6 +43,7 @@ class WebHook < ActiveRecord::Base
WebHook.post(post_url,
body: data.to_json,
headers: {"Content-Type" => "application/json"},
+ verify: false,
basic_auth: auth)
end
end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index a7e6fea5ad..b8a0a9eb58 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -19,7 +19,7 @@ class WikiPage
validates :title, presence: true
validates :content, presence: true
- # The Gitlab GollumWiki instance.
+ # The Gitlab ProjectWiki instance.
attr_reader :wiki
# The raw Gollum::Page instance.
@@ -47,7 +47,11 @@ class WikiPage
# The formatted title of this page.
def title
- @attributes[:title] || ""
+ if @attributes[:title]
+ @attributes[:title].gsub(/-+/, ' ')
+ else
+ ""
+ end
end
# Sets the title of this page.
@@ -57,12 +61,16 @@ class WikiPage
# The raw content of this page.
def content
- @attributes[:content]
+ @attributes[:content] ||= if @page
+ @page.raw_data
+ end
end
# The processed/formatted content of this page.
def formatted_content
- @attributes[:formatted_content]
+ @attributes[:formatted_content] ||= if @page
+ @page.formatted_data
+ end
end
# The markup format for the page.
@@ -118,7 +126,7 @@ class WikiPage
# :content - The raw markup content.
# :format - Optional symbol representing the
# content format. Can be any type
- # listed in the GollumWiki::MARKUPS
+ # listed in the ProjectWiki::MARKUPS
# Hash.
# :message - Optional commit message to set on
# the new page.
@@ -135,7 +143,7 @@ class WikiPage
#
# new_content - The raw markup content to replace the existing.
# format - Optional symbol representing the content format.
- # See GollumWiki::MARKUPS Hash for available formats.
+ # See ProjectWiki::MARKUPS Hash for available formats.
# message - Optional commit message to set on the new version.
#
# Returns the String SHA1 of the newly created page
@@ -163,23 +171,30 @@ class WikiPage
def set_attributes
attributes[:slug] = @page.escaped_url_path
attributes[:title] = @page.title
- attributes[:content] = @page.raw_data
- attributes[:formatted_content] = @page.formatted_data
attributes[:format] = @page.format
end
def save(method, *args)
- if valid? && wiki.send(method, *args)
- @page = wiki.wiki.paged(title)
+ project_wiki = wiki
+ if valid? && project_wiki.send(method, *args)
+
+ page_details = if method == :update_page
+ @page.path
+ else
+ title
+ end
+
+ page_title, page_dir = project_wiki.page_title_and_dir(page_details)
+ gollum_wiki = project_wiki.wiki
+ @page = gollum_wiki.paged(page_title, page_dir)
set_attributes
@persisted = true
else
- errors.add(:base, wiki.error_message) if wiki.error_message
+ errors.add(:base, project_wiki.error_message) if project_wiki.error_message
@persisted = false
end
@persisted
end
-
end
diff --git a/app/observers/activity_observer.rb b/app/observers/activity_observer.rb
deleted file mode 100644
index d754ac8185..0000000000
--- a/app/observers/activity_observer.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-class ActivityObserver < BaseObserver
- observe :issue, :note, :milestone
-
- def after_create(record)
- event_author_id = record.author_id
-
- if record.kind_of?(Note)
- # Skip system notes, like status changes and cross-references.
- return true if record.system?
-
- # Skip wall notes to prevent spamming of dashboard
- return true if record.noteable_type.blank?
- end
-
- if event_author_id
- create_event(record, Event.determine_action(record))
- end
- end
-
- def after_close(record, transition)
- create_event(record, Event::CLOSED)
- end
-
- def after_reopen(record, transition)
- create_event(record, Event::REOPENED)
- end
-
- protected
-
- def create_event(record, status)
- Event.create(
- project: record.project,
- target_id: record.id,
- target_type: record.class.name,
- action: status,
- author_id: current_user.id
- )
- end
-end
diff --git a/app/observers/base_observer.rb b/app/observers/base_observer.rb
index f9a0242ce7..d685bd5d81 100644
--- a/app/observers/base_observer.rb
+++ b/app/observers/base_observer.rb
@@ -3,6 +3,10 @@ class BaseObserver < ActiveRecord::Observer
NotificationService.new
end
+ def event_service
+ EventCreateService.new
+ end
+
def log_info message
Gitlab::AppLogger.info message
end
diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb
deleted file mode 100644
index 886d8b776f..0000000000
--- a/app/observers/issue_observer.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class IssueObserver < BaseObserver
- def after_create(issue)
- notification.new_issue(issue, current_user)
-
- issue.create_cross_references!(issue.project, current_user)
- end
-
- def after_close(issue, transition)
- notification.close_issue(issue, current_user)
-
- create_note(issue)
- end
-
- def after_reopen(issue, transition)
- create_note(issue)
- end
-
- def after_update(issue)
- if issue.is_being_reassigned?
- notification.reassigned_issue(issue, current_user)
- end
-
- issue.notice_added_references(issue.project, current_user)
- end
-
- protected
-
- # Create issue note with service comment like 'Status changed to closed'
- def create_note(issue)
- Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
- end
-end
diff --git a/app/observers/key_observer.rb b/app/observers/key_observer.rb
deleted file mode 100644
index c013594e78..0000000000
--- a/app/observers/key_observer.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class KeyObserver < BaseObserver
- def after_create(key)
- GitlabShellWorker.perform_async(
- :add_key,
- key.shell_id,
- key.key
- )
-
- notification.new_key(key)
- end
-
- def after_destroy(key)
- GitlabShellWorker.perform_async(
- :remove_key,
- key.shell_id,
- key.key,
- )
- end
-end
diff --git a/app/observers/merge_request_observer.rb b/app/observers/merge_request_observer.rb
deleted file mode 100644
index d70da514cd..0000000000
--- a/app/observers/merge_request_observer.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-class MergeRequestObserver < ActivityObserver
- observe :merge_request
-
- def after_create(merge_request)
- if merge_request.author_id
- create_event(merge_request, Event.determine_action(merge_request))
- end
-
- notification.new_merge_request(merge_request, current_user)
-
- merge_request.create_cross_references!(merge_request.project, current_user)
- end
-
- def after_close(merge_request, transition)
- create_event(merge_request, Event::CLOSED)
- Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
-
- notification.close_mr(merge_request, current_user)
- end
-
- def after_merge(merge_request, transition)
- notification.merge_mr(merge_request)
- # Since MR can be merged via sidekiq
- # to prevent event duplication do this check
- return true if merge_request.merge_event
-
- Event.create(
- project: merge_request.target_project,
- target_id: merge_request.id,
- target_type: merge_request.class.name,
- action: Event::MERGED,
- author_id: merge_request.author_id_of_changes
- )
- end
-
- def after_reopen(merge_request, transition)
- create_event(merge_request, Event::REOPENED)
- Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
- end
-
- def after_update(merge_request)
- notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
-
- merge_request.notice_added_references(merge_request.project, current_user)
- end
-
- def create_event(record, status)
- Event.create(
- project: record.target_project,
- target_id: record.id,
- target_type: record.class.name,
- action: status,
- author_id: current_user.id
- )
- end
-end
diff --git a/app/observers/milestone_observer.rb b/app/observers/milestone_observer.rb
new file mode 100644
index 0000000000..a1a62a99a8
--- /dev/null
+++ b/app/observers/milestone_observer.rb
@@ -0,0 +1,13 @@
+class MilestoneObserver < BaseObserver
+ def after_create(milestone)
+ event_service.open_milestone(milestone, current_user)
+ end
+
+ def after_close(milestone, transition)
+ event_service.close_milestone(milestone, current_user)
+ end
+
+ def after_reopen(milestone, transition)
+ event_service.reopen_milestone(milestone, current_user)
+ end
+end
diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb
index d31b6e8d7c..337bb1dc5a 100644
--- a/app/observers/note_observer.rb
+++ b/app/observers/note_observer.rb
@@ -2,6 +2,12 @@ class NoteObserver < BaseObserver
def after_create(note)
notification.new_note(note)
+ # Skip system notes, like status changes and cross-references.
+ # Skip wall notes to prevent spamming of dashboard
+ if note.noteable_type.present? && !note.system
+ event_service.leave_note(note, current_user)
+ end
+
unless note.system?
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb
index f301f30645..ad41ddad58 100644
--- a/app/observers/project_observer.rb
+++ b/app/observers/project_observer.rb
@@ -1,41 +1,11 @@
class ProjectObserver < BaseObserver
def after_create(project)
- project.update_column(:last_activity_at, project.created_at)
-
- return true if project.forked?
-
- if project.import?
- RepositoryImportWorker.perform_in(5.seconds, project.id)
- else
- GitlabShellWorker.perform_async(
- :add_repository,
- project.path_with_namespace
- )
-
- log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
- end
-
- if project.wiki_enabled?
- begin
- # force the creation of a wiki,
- GollumWiki.new(project, project.owner).wiki
- rescue GollumWiki::CouldNotCreateWikiError => ex
- # Prevent project observer crash
- # if failed to create wiki
- nil
- end
- end
+ log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
end
def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
project.rename_repo if project.path_changed?
-
- GitlabShellWorker.perform_async(
- :update_repository_head,
- project.path_with_namespace,
- project.default_branch
- ) if project.default_branch_changed?
end
def before_destroy(project)
diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb
index 3a649fd590..80de177b9a 100644
--- a/app/observers/system_hook_observer.rb
+++ b/app/observers/system_hook_observer.rb
@@ -2,10 +2,16 @@ class SystemHookObserver < BaseObserver
observe :user, :project, :users_project
def after_create(model)
- SystemHooksService.execute_hooks_for(model, :create)
+ system_hook_service.execute_hooks_for(model, :create)
end
def after_destroy(model)
- SystemHooksService.execute_hooks_for(model, :destroy)
+ system_hook_service.execute_hooks_for(model, :destroy)
+ end
+
+ private
+
+ def system_hook_service
+ SystemHooksService.new
end
end
diff --git a/app/observers/users_group_observer.rb b/app/observers/users_group_observer.rb
index ecdbede89d..42a05b5e17 100644
--- a/app/observers/users_group_observer.rb
+++ b/app/observers/users_group_observer.rb
@@ -4,6 +4,6 @@ class UsersGroupObserver < BaseObserver
end
def after_update(membership)
- notification.update_group_member(membership)
+ notification.update_group_member(membership) if membership.group_access_changed?
end
end
diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb
index ca9649c76a..44c72b3018 100644
--- a/app/observers/users_project_observer.rb
+++ b/app/observers/users_project_observer.rb
@@ -1,8 +1,4 @@
class UsersProjectObserver < BaseObserver
- def after_commit(users_project)
- return if users_project.destroyed?
- end
-
def after_create(users_project)
Event.create(
project_id: users_project.project.id,
@@ -14,7 +10,7 @@ class UsersProjectObserver < BaseObserver
end
def after_update(users_project)
- notification.update_team_member(users_project)
+ notification.update_team_member(users_project) if users_project.project_access_changed?
end
def after_destroy(users_project)
diff --git a/app/contexts/base_context.rb b/app/services/base_service.rb
similarity index 69%
rename from app/contexts/base_context.rb
rename to app/services/base_service.rb
index 101be50d54..9ad8092315 100644
--- a/app/contexts/base_context.rb
+++ b/app/services/base_service.rb
@@ -1,4 +1,4 @@
-class BaseContext
+class BaseService
attr_accessor :project, :current_user, :params
def initialize(project, user, params)
@@ -16,5 +16,16 @@ class BaseContext
def can?(object, action, subject)
abilities.allowed?(object, action, subject)
end
-end
+ def notification_service
+ NotificationService.new
+ end
+
+ def event_service
+ EventCreateService.new
+ end
+
+ def log_info message
+ Gitlab::AppLogger.info message
+ end
+end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
new file mode 100644
index 0000000000..98beeee835
--- /dev/null
+++ b/app/services/create_branch_service.rb
@@ -0,0 +1,13 @@
+class CreateBranchService
+ def execute(project, branch_name, ref, current_user)
+ repository = project.repository
+ repository.add_branch(branch_name, ref)
+ new_branch = repository.find_branch(branch_name)
+
+ if new_branch
+ Event.create_ref_event(project, current_user, new_branch, 'add')
+ end
+
+ new_branch
+ end
+end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
new file mode 100644
index 0000000000..8d8a5873e6
--- /dev/null
+++ b/app/services/event_create_service.rb
@@ -0,0 +1,64 @@
+# EventCreateService class
+#
+# Used for creating events feed on dashboard after certain user action
+#
+# Ex.
+# EventCreateService.new.new_issue(issue, current_user)
+#
+class EventCreateService
+ def open_issue(issue, current_user)
+ create_event(issue, current_user, Event::CREATED)
+ end
+
+ def close_issue(issue, current_user)
+ create_event(issue, current_user, Event::CLOSED)
+ end
+
+ def reopen_issue(issue, current_user)
+ create_event(issue, current_user, Event::REOPENED)
+ end
+
+ def open_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::CREATED)
+ end
+
+ def close_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::CLOSED)
+ end
+
+ def reopen_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::REOPENED)
+ end
+
+ def merge_mr(merge_request, current_user)
+ create_event(merge_request, current_user, Event::MERGED)
+ end
+
+ def open_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::CREATED)
+ end
+
+ def close_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::CLOSED)
+ end
+
+ def reopen_milestone(milestone, current_user)
+ create_event(milestone, current_user, Event::REOPENED)
+ end
+
+ def leave_note(note, current_user)
+ create_event(note, current_user, Event::COMMENTED)
+ end
+
+ private
+
+ def create_event(record, current_user, status)
+ Event.create(
+ project: record.project,
+ target_id: record.id,
+ target_type: record.class.name,
+ action: status,
+ author_id: current_user.id
+ )
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
new file mode 100644
index 0000000000..f1765d3897
--- /dev/null
+++ b/app/services/files/base_service.rb
@@ -0,0 +1,31 @@
+module Files
+ class BaseService < ::BaseService
+ attr_reader :ref, :path
+
+ def initialize(project, user, params, ref, path = nil)
+ @project, @current_user, @params = project, user, params.dup
+ @ref = ref
+ @path = path
+ end
+
+ private
+
+ def error(message)
+ {
+ error: message,
+ status: :error
+ }
+ end
+
+ def success
+ {
+ error: '',
+ status: :success
+ }
+ end
+
+ def repository
+ project.repository
+ end
+ end
+end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
new file mode 100644
index 0000000000..1998fb79e7
--- /dev/null
+++ b/app/services/files/create_service.rb
@@ -0,0 +1,47 @@
+require_relative "base_service"
+
+module Files
+ class CreateService < BaseService
+ def execute
+ allowed = if project.protected_branch?(ref)
+ can?(current_user, :push_code_to_protected_branches, project)
+ else
+ can?(current_user, :push_code, project)
+ end
+
+ unless allowed
+ return error("You are not allowed to create file in this branch")
+ end
+
+ unless repository.branch_names.include?(ref)
+ return error("You can only create files if you are on top of a branch")
+ end
+
+ file_name = File.basename(path)
+ file_path = path
+
+ unless file_name =~ Gitlab::Regex.path_regex
+ return error("Your changes could not be committed, because file name contains not allowed characters")
+ end
+
+ blob = repository.blob_at_branch(ref, file_path)
+
+ if blob
+ return error("Your changes could not be committed, because file with such name exists")
+ end
+
+ new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
+ created_successfully = new_file_action.commit!(
+ params[:content],
+ params[:commit_message],
+ params[:encoding]
+ )
+
+ if created_successfully
+ success
+ else
+ error("Your changes could not be committed, because the file has been changed")
+ end
+ end
+ end
+end
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
new file mode 100644
index 0000000000..ff5dc6ef34
--- /dev/null
+++ b/app/services/files/delete_service.rb
@@ -0,0 +1,40 @@
+require_relative "base_service"
+
+module Files
+ class DeleteService < BaseService
+ def execute
+ allowed = if project.protected_branch?(ref)
+ can?(current_user, :push_code_to_protected_branches, project)
+ else
+ can?(current_user, :push_code, project)
+ end
+
+ unless allowed
+ return error("You are not allowed to push into this branch")
+ end
+
+ unless repository.branch_names.include?(ref)
+ return error("You can only create files if you are on top of a branch")
+ end
+
+ blob = repository.blob_at_branch(ref, path)
+
+ unless blob
+ return error("You can only edit text files")
+ end
+
+ delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
+
+ deleted_successfully = delete_file_action.commit!(
+ nil,
+ params[:commit_message]
+ )
+
+ if deleted_successfully
+ success
+ else
+ error("Your changes could not be committed, because the file has been changed")
+ end
+ end
+ end
+end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
new file mode 100644
index 0000000000..c631f28749
--- /dev/null
+++ b/app/services/files/update_service.rb
@@ -0,0 +1,40 @@
+require_relative "base_service"
+
+module Files
+ class UpdateService < BaseService
+ def execute
+ allowed = if project.protected_branch?(ref)
+ can?(current_user, :push_code_to_protected_branches, project)
+ else
+ can?(current_user, :push_code, project)
+ end
+
+ unless allowed
+ return error("You are not allowed to push into this branch")
+ end
+
+ unless repository.branch_names.include?(ref)
+ return error("You can only create files if you are on top of a branch")
+ end
+
+ blob = repository.blob_at_branch(ref, path)
+
+ unless blob
+ return error("You can only edit text files")
+ end
+
+ edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
+ created_successfully = edit_file_action.commit!(
+ params[:content],
+ params[:commit_message],
+ params[:encoding]
+ )
+
+ if created_successfully
+ success
+ else
+ error("Your changes could not be committed, because the file has been changed")
+ end
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 3ad41de397..715b569075 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -24,8 +24,8 @@ class GitPushService
create_push_event
project.ensure_satellite_exists
- project.discover_default_branch
project.repository.expire_cache
+ project.update_repository_size
if push_to_existing_branch?(ref, oldrev)
project.update_merge_requests(oldrev, newrev, ref, @user)
@@ -33,7 +33,7 @@ class GitPushService
end
if push_to_branch?(ref)
- project.execute_hooks(@push_data.dup)
+ project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup)
end
@@ -87,10 +87,9 @@ class GitPushService
author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch
- Thread.current[:current_user] = author
- Thread.current[:current_commit] = commit
-
- issues_to_close.each { |i| i.close && i.save }
+ issues_to_close.each do |issue|
+ Issues::CloseService.new(project, author, {}).execute(issue, commit)
+ end
end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
@@ -189,8 +188,6 @@ class GitPushService
end
def commit_user commit
- User.where(email: commit.author_email).first ||
- User.where(name: commit.author_name).first ||
- user
+ User.find_for_commit(commit.author_email, commit.author_name) || user
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
new file mode 100644
index 0000000000..62eaf9b4f5
--- /dev/null
+++ b/app/services/git_tag_push_service.rb
@@ -0,0 +1,40 @@
+class GitTagPushService
+ attr_accessor :project, :user, :push_data
+
+ def execute(project, user, oldrev, newrev, ref)
+ @project, @user = project, user
+ @push_data = create_push_data(oldrev, newrev, ref)
+
+ create_push_event
+ project.repository.expire_cache
+ project.execute_hooks(@push_data.dup, :tag_push_hooks)
+ end
+
+ private
+
+ def create_push_data(oldrev, newrev, ref)
+ data = {
+ ref: ref,
+ before: oldrev,
+ after: newrev,
+ user_id: user.id,
+ user_name: user.name,
+ project_id: project.id,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url
+ }
+ }
+ end
+
+ def create_push_event
+ Event.create!(
+ project: project,
+ action: Event::PUSHED,
+ data: push_data,
+ author_id: push_data[:user_id]
+ )
+ end
+end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
new file mode 100644
index 0000000000..2e1e1f7e0f
--- /dev/null
+++ b/app/services/issues/base_service.rb
@@ -0,0 +1,14 @@
+module Issues
+ class BaseService < ::BaseService
+
+ private
+
+ def create_assignee_note(issue)
+ Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
+ end
+
+ def execute_hooks(issue)
+ issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
+ end
+ end
+end
diff --git a/app/contexts/issues/bulk_update_context.rb b/app/services/issues/bulk_update_service.rb
similarity index 91%
rename from app/contexts/issues/bulk_update_context.rb
rename to app/services/issues/bulk_update_service.rb
index 73a3c35352..f72a346af6 100644
--- a/app/contexts/issues/bulk_update_context.rb
+++ b/app/services/issues/bulk_update_service.rb
@@ -1,5 +1,5 @@
module Issues
- class BulkUpdateContext < BaseContext
+ class BulkUpdateService < BaseService
def execute
update_data = params[:update]
@@ -22,7 +22,7 @@ module Issues
opts[:milestone_id] = milestone_id if milestone_id.present?
opts[:assignee_id] = assignee_id if assignee_id.present?
- issues = Issue.where(id: issues_ids).all
+ issues = Issue.where(id: issues_ids)
issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
issues.each do |issue|
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
new file mode 100644
index 0000000000..85c0226cca
--- /dev/null
+++ b/app/services/issues/close_service.rb
@@ -0,0 +1,20 @@
+module Issues
+ class CloseService < Issues::BaseService
+ def execute(issue, commit = nil)
+ if issue.close
+ notification_service.close_issue(issue, current_user)
+ event_service.close_issue(issue, current_user)
+ create_note(issue, commit)
+ execute_hooks(issue)
+ end
+
+ issue
+ end
+
+ private
+
+ def create_note(issue, current_commit)
+ Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
+ end
+ end
+end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
new file mode 100644
index 0000000000..d6137833bb
--- /dev/null
+++ b/app/services/issues/create_service.rb
@@ -0,0 +1,17 @@
+module Issues
+ class CreateService < Issues::BaseService
+ def execute
+ issue = project.issues.new(params)
+ issue.author = current_user
+
+ if issue.save
+ notification_service.new_issue(issue, current_user)
+ event_service.open_issue(issue, current_user)
+ issue.create_cross_references!(issue.project, current_user)
+ execute_hooks(issue)
+ end
+
+ issue
+ end
+ end
+end
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
new file mode 100644
index 0000000000..a931398aff
--- /dev/null
+++ b/app/services/issues/reopen_service.rb
@@ -0,0 +1,19 @@
+module Issues
+ class ReopenService < Issues::BaseService
+ def execute(issue)
+ if issue.reopen
+ event_service.reopen_issue(issue, current_user)
+ create_note(issue)
+ execute_hooks(issue)
+ end
+
+ issue
+ end
+
+ private
+
+ def create_note(issue)
+ Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
+ end
+ end
+end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
new file mode 100644
index 0000000000..b562c401fd
--- /dev/null
+++ b/app/services/issues/update_service.rb
@@ -0,0 +1,28 @@
+module Issues
+ class UpdateService < Issues::BaseService
+ def execute(issue)
+ state = params.delete('state_event')
+
+ case state
+ when 'reopen'
+ Issues::ReopenService.new(project, current_user, {}).execute(issue)
+ when 'close'
+ Issues::CloseService.new(project, current_user, {}).execute(issue)
+ end
+
+ if params.present? && issue.update_attributes(params)
+ issue.reset_events_cache
+
+ if issue.previous_changes.include?('assignee_id')
+ notification_service.reassigned_issue(issue, current_user)
+ create_assignee_note(issue)
+ end
+
+ issue.notice_added_references(issue.project, current_user)
+ execute_hooks(issue)
+ end
+
+ issue
+ end
+ end
+end
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
new file mode 100644
index 0000000000..e35c03275f
--- /dev/null
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -0,0 +1,29 @@
+module MergeRequests
+ # AutoMergeService class
+ #
+ # Do git merge in satellite and in case of success
+ # mark merge request as merged and execute all hooks and notifications
+ # Called when you do merge via GitLab UI
+ class AutoMergeService < BaseMergeService
+ def execute(merge_request, current_user, commit_message)
+ merge_request.lock
+
+ if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
+ merge_request.merge
+
+ notification.merge_mr(merge_request, current_user)
+ create_merge_event(merge_request, current_user)
+ execute_project_hooks(merge_request)
+
+ true
+ else
+ merge_request.unlock
+ false
+ end
+ rescue
+ merge_request.unlock if merge_request.locked?
+ merge_request.mark_as_unmergeable
+ false
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
new file mode 100644
index 0000000000..9bc50d3d16
--- /dev/null
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -0,0 +1,20 @@
+module MergeRequests
+ class BaseMergeService
+
+ private
+
+ def notification
+ NotificationService.new
+ end
+
+ def create_merge_event(merge_request, current_user)
+ EventCreateService.new.merge_mr(merge_request, current_user)
+ end
+
+ def execute_project_hooks(merge_request)
+ if merge_request.project
+ merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
new file mode 100644
index 0000000000..c77f5d664e
--- /dev/null
+++ b/app/services/merge_requests/base_service.rb
@@ -0,0 +1,20 @@
+module MergeRequests
+ class BaseService < ::BaseService
+
+ private
+
+ def create_assignee_note(merge_request)
+ Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
+ end
+
+ def create_note(merge_request)
+ Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
+ end
+
+ def execute_hooks(merge_request)
+ if merge_request.project
+ merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
new file mode 100644
index 0000000000..64e37a23e6
--- /dev/null
+++ b/app/services/merge_requests/close_service.rb
@@ -0,0 +1,18 @@
+module MergeRequests
+ class CloseService < MergeRequests::BaseService
+ def execute(merge_request, commit = nil)
+ # If we close MergeRequest we want to ignore validation
+ # so we can close broken one (Ex. fork project removed)
+ merge_request.allow_broken = true
+
+ if merge_request.close
+ event_service.close_mr(merge_request, current_user)
+ notification_service.close_mr(merge_request, current_user)
+ create_note(merge_request)
+ execute_hooks(merge_request)
+ end
+
+ merge_request
+ end
+ end
+end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
new file mode 100644
index 0000000000..d1bf827f3f
--- /dev/null
+++ b/app/services/merge_requests/create_service.rb
@@ -0,0 +1,19 @@
+module MergeRequests
+ class CreateService < MergeRequests::BaseService
+ def execute
+ merge_request = MergeRequest.new(params)
+ merge_request.source_project = project
+ merge_request.target_project ||= project
+ merge_request.author = current_user
+
+ if merge_request.save
+ event_service.open_mr(merge_request, current_user)
+ notification_service.new_merge_request(merge_request, current_user)
+ merge_request.create_cross_references!(merge_request.project, current_user)
+ execute_hooks(merge_request)
+ end
+
+ merge_request
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
new file mode 100644
index 0000000000..680766140b
--- /dev/null
+++ b/app/services/merge_requests/merge_service.rb
@@ -0,0 +1,21 @@
+module MergeRequests
+ # MergeService class
+ #
+ # Mark existing merge request as merged
+ # and execute all hooks and notifications
+ # Called when you do merge via command line and push code
+ # to target branch
+ class MergeService < BaseMergeService
+ def execute(merge_request, current_user, commit_message)
+ merge_request.merge
+
+ notification.merge_mr(merge_request, current_user)
+ create_merge_event(merge_request, current_user)
+ execute_project_hooks(merge_request)
+
+ true
+ rescue
+ false
+ end
+ end
+end
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
new file mode 100644
index 0000000000..2eb13d3e0e
--- /dev/null
+++ b/app/services/merge_requests/reopen_service.rb
@@ -0,0 +1,15 @@
+module MergeRequests
+ class ReopenService < MergeRequests::BaseService
+ def execute(merge_request)
+ if merge_request.reopen
+ event_service.reopen_mr(merge_request, current_user)
+ create_note(merge_request)
+ execute_hooks(merge_request)
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
+ end
+
+ merge_request
+ end
+ end
+end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
new file mode 100644
index 0000000000..bbedca6ffd
--- /dev/null
+++ b/app/services/merge_requests/update_service.rb
@@ -0,0 +1,37 @@
+require_relative 'base_service'
+require_relative 'reopen_service'
+require_relative 'close_service'
+
+module MergeRequests
+ class UpdateService < MergeRequests::BaseService
+ def execute(merge_request)
+ # We dont allow change of source/target projects
+ # after merge request was created
+ params.delete(:source_project_id)
+ params.delete(:target_project_id)
+
+ state = params.delete('state_event')
+
+ case state
+ when 'reopen'
+ MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
+ when 'close'
+ MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
+ end
+
+ if params.present? && merge_request.update_attributes(params)
+ merge_request.reset_events_cache
+
+ if merge_request.previous_changes.include?('assignee_id')
+ notification_service.reassigned_merge_request(merge_request, current_user)
+ create_assignee_note(merge_request)
+ end
+
+ merge_request.notice_added_references(merge_request.project, current_user)
+ execute_hooks(merge_request)
+ end
+
+ merge_request
+ end
+ end
+end
diff --git a/app/contexts/notes/create_context.rb b/app/services/notes/create_service.rb
similarity index 71%
rename from app/contexts/notes/create_context.rb
rename to app/services/notes/create_service.rb
index 36ea76ff94..ff6dfb6113 100644
--- a/app/contexts/notes/create_context.rb
+++ b/app/services/notes/create_service.rb
@@ -1,8 +1,9 @@
module Notes
- class CreateContext < BaseContext
+ class CreateService < BaseService
def execute
note = project.notes.new(params[:note])
note.author = current_user
+ note.system = false
note.save
note
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 750a71aea6..06140c5afe 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -17,9 +17,16 @@ class NotificationService
end
end
+ # Always notify user about email added to profile
+ def new_email(email)
+ if email.user
+ mailer.new_email_email(email.id)
+ end
+ end
+
# When create an issue we should send next emails:
#
- # * issue assignee if his notification level is not Disabled
+ # * issue assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating
#
def new_issue(issue, current_user)
@@ -28,8 +35,8 @@ class NotificationService
# When we close an issue we should send next emails:
#
- # * issue author if his notification level is not Disabled
- # * issue assignee if his notification level is not Disabled
+ # * issue author if their notification level is not Disabled
+ # * issue assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating
#
def close_issue(issue, current_user)
@@ -38,8 +45,8 @@ class NotificationService
# When we reassign an issue we should send next emails:
#
- # * issue old assignee if his notification level is not Disabled
- # * issue new assignee if his notification level is not Disabled
+ # * issue old assignee if their notification level is not Disabled
+ # * issue new assignee if their notification level is not Disabled
#
def reassigned_issue(issue, current_user)
reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email')
@@ -48,7 +55,7 @@ class NotificationService
# When create a merge request we should send next emails:
#
- # * mr assignee if his notification level is not Disabled
+ # * mr assignee if their notification level is not Disabled
#
def new_merge_request(merge_request, current_user)
new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email')
@@ -56,8 +63,8 @@ class NotificationService
# When we reassign a merge_request we should send next emails:
#
- # * merge_request old assignee if his notification level is not Disabled
- # * merge_request assignee if his notification level is not Disabled
+ # * merge_request old assignee if their notification level is not Disabled
+ # * merge_request assignee if their notification level is not Disabled
#
def reassigned_merge_request(merge_request, current_user)
reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email')
@@ -65,8 +72,8 @@ class NotificationService
# When we close a merge request we should send next emails:
#
- # * merge_request author if his notification level is not Disabled
- # * merge_request assignee if his notification level is not Disabled
+ # * merge_request author if their notification level is not Disabled
+ # * merge_request assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating
#
def close_mr(merge_request, current_user)
@@ -75,16 +82,16 @@ class NotificationService
# When we merge a merge request we should send next emails:
#
- # * merge_request author if his notification level is not Disabled
- # * merge_request assignee if his notification level is not Disabled
+ # * merge_request author if their notification level is not Disabled
+ # * merge_request assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating
#
- def merge_mr(merge_request)
+ def merge_mr(merge_request, current_user)
recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
recipients.each do |recipient|
- mailer.merged_merge_request_email(recipient.id, merge_request.id)
+ mailer.merged_merge_request_email(recipient.id, merge_request.id, current_user.id)
end
end
@@ -104,6 +111,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/
+ return true if note.note =~ /\A_mentioned in / && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
@@ -157,25 +165,90 @@ class NotificationService
mailer.group_access_granted_email(users_group.id)
end
+ def project_was_moved(project)
+ recipients = project.team.members
+ recipients = reject_muted_users(recipients, project)
+
+ recipients.each do |recipient|
+ mailer.project_was_moved_email(project.id, recipient.id)
+ end
+ end
+
protected
# Get project users with WATCH notification level
def project_watchers(project)
- project_watchers = []
- member_methods = { project => :users_projects }
- member_methods.merge!(project.group => :users_groups) if project.group
+ project_members = users_project_notification(project)
- member_methods.each do |object, member_method|
- # Get project notification settings since it has higher priority
- user_ids = object.send(member_method).where(notification_level: Notification::N_WATCH).pluck(:user_id)
- project_watchers += User.where(id: user_ids)
+ users_with_project_level_global = users_project_notification(project, Notification::N_GLOBAL)
+ users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL)
+ users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
- # next collect users who use global settings with watch state
- user_ids = object.send(member_method).where(notification_level: Notification::N_GLOBAL).pluck(:user_id)
- project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH)
+ users_with_project_setting = select_users_project_setting(project, users_with_project_level_global, users)
+ users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users)
+
+ User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
+ end
+
+ def users_project_notification(project, notification_level=nil)
+ project_members = project.users_projects
+
+ if notification_level
+ project_members.where(notification_level: notification_level).pluck(:user_id)
+ else
+ project_members.pluck(:user_id)
+ end
+ end
+
+ def users_group_notification(project, notification_level)
+ if project.group
+ project.group.users_groups.where(notification_level: notification_level).pluck(:user_id)
+ else
+ []
+ end
+ end
+
+ def users_with_global_level_watch(ids)
+ User.where(
+ id: ids,
+ notification_level: Notification::N_WATCH
+ ).pluck(:id)
+ end
+
+ # Build a list of users based on project notifcation settings
+ def select_users_project_setting(project, global_setting, users_global_level_watch)
+ users = users_project_notification(project, Notification::N_WATCH)
+
+ # If project setting is global, add to watch list if global setting is watch
+ global_setting.each do |user_id|
+ if users_global_level_watch.include?(user_id)
+ users << user_id
+ end
end
- project_watchers.uniq
+ users
+ end
+
+ # Build a list of users based on group notifcation settings
+ def select_users_group_setting(project, project_members, global_setting, users_global_level_watch)
+ uids = users_group_notification(project, Notification::N_WATCH)
+
+ # Group setting is watch, add to users list if user is not project member
+ users = []
+ uids.each do |user_id|
+ if project_members.exclude?(user_id)
+ users << user_id
+ end
+ end
+
+ # Group setting is global, add to users list if global setting is watch
+ global_setting.each do |user_id|
+ if project_members.exclude?(user_id) && users_global_level_watch.include?(user_id)
+ users << user_id
+ end
+ end
+
+ users
end
# Remove users with disabled notifications from array
@@ -186,10 +259,10 @@ class NotificationService
users.reject do |user|
next user.notification.disabled? unless project
- tm = project.users_projects.find_by_user_id(user.id)
+ tm = project.users_projects.find_by(user_id: user.id)
if !tm && project.group
- tm = project.group.users_groups.find_by_user_id(user.id)
+ tm = project.group.users_groups.find_by(user_id: user.id)
end
# reject users who globally disabled notification and has no membership
@@ -241,7 +314,7 @@ class NotificationService
recipients.delete(current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, target.assignee_id_was)
+ mailer.send(method, recipient.id, target.id, target.assignee_id_was, current_user.id)
end
end
diff --git a/app/services/project_transfer_service.rb b/app/services/project_transfer_service.rb
index 7150c1c78c..7055ef32ae 100644
--- a/app/services/project_transfer_service.rb
+++ b/app/services/project_transfer_service.rb
@@ -18,6 +18,10 @@ class ProjectTransferService
raise TransferError.new("Project with same path in target namespace already exists")
end
+ # Remove old satellite
+ project.satellite.destroy
+
+ # Apply new namespace id
project.namespace = new_namespace
project.save!
@@ -29,8 +33,8 @@ class ProjectTransferService
# Move wiki repo also if present
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
- # create satellite repo
- project.ensure_satellite_exists
+ # Create a new satellite (reload project from DB)
+ Project.find(project.id).ensure_satellite_exists
# clear project cached events
project.reset_events_cache
diff --git a/app/contexts/projects/create_context.rb b/app/services/projects/create_service.rb
similarity index 66%
rename from app/contexts/projects/create_context.rb
rename to app/services/projects/create_service.rb
index 1c60a5de14..11b6525650 100644
--- a/app/contexts/projects/create_context.rb
+++ b/app/services/projects/create_service.rb
@@ -1,5 +1,5 @@
module Projects
- class CreateContext < BaseContext
+ class CreateService < BaseService
def initialize(user, params)
@current_user, @params = user, params.dup
end
@@ -8,6 +8,11 @@ module Projects
# get namespace id
namespace_id = params.delete(:namespace_id)
+ # check that user is allowed to set specified visibility_level
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ params.delete(:visibility_level)
+ end
+
# Load default feature settings
default_features = Gitlab.config.gitlab.default_projects_features
@@ -17,7 +22,7 @@ module Projects
wall_enabled: default_features.wall,
snippets_enabled: default_features.snippets,
merge_requests_enabled: default_features.merge_requests,
- public: default_features.public
+ visibility_level: default_features.visibility_level
}.stringify_keys
@project = Project.new(default_opts.merge(params))
@@ -47,14 +52,35 @@ module Projects
@project.creator = current_user
if @project.save
- @project.discover_default_branch
-
unless @project.group
@project.users_projects.create(
project_access: UsersProject::MASTER,
user: current_user
)
end
+
+ @project.update_column(:last_activity_at, @project.created_at)
+
+ if @project.import?
+ @project.import_start
+ else
+ GitlabShellWorker.perform_async(
+ :add_repository,
+ @project.path_with_namespace
+ )
+
+ end
+
+ if @project.wiki_enabled?
+ begin
+ # force the creation of a wiki,
+ ProjectWiki.new(@project, @project.owner).wiki
+ rescue ProjectWiki::CouldNotCreateWikiError => ex
+ # Prevent project observer crash
+ # if failed to create wiki
+ nil
+ end
+ end
end
@project
@@ -70,7 +96,7 @@ module Projects
end
def allowed_namespace?(user, namespace_id)
- namespace = Namespace.find_by_id(namespace_id)
+ namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:manage_namespace, namespace)
end
end
diff --git a/app/contexts/projects/fork_context.rb b/app/services/projects/fork_service.rb
similarity index 97%
rename from app/contexts/projects/fork_context.rb
rename to app/services/projects/fork_service.rb
index fbc67220d5..2f1c7b18aa 100644
--- a/app/contexts/projects/fork_context.rb
+++ b/app/services/projects/fork_service.rb
@@ -1,5 +1,5 @@
module Projects
- class ForkContext < BaseContext
+ class ForkService < BaseService
include Gitlab::ShellAdapter
def initialize(project, user)
diff --git a/app/contexts/projects/transfer_context.rb b/app/services/projects/transfer_service.rb
similarity index 94%
rename from app/contexts/projects/transfer_context.rb
rename to app/services/projects/transfer_service.rb
index 3011984e3f..f8e27f6f1c 100644
--- a/app/contexts/projects/transfer_context.rb
+++ b/app/services/projects/transfer_service.rb
@@ -1,5 +1,5 @@
module Projects
- class TransferContext < BaseContext
+ class TransferService < BaseService
def execute(role = :default)
namespace_id = params[:project].delete(:namespace_id)
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
new file mode 100644
index 0000000000..d9d371da5c
--- /dev/null
+++ b/app/services/projects/update_service.rb
@@ -0,0 +1,19 @@
+module Projects
+ class UpdateService < BaseService
+ def execute(role = :default)
+ params[:project].delete(:namespace_id)
+ # check that user is allowed to set specified visibility_level
+ unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level])
+ params[:project].delete(:visibility_level)
+ end
+
+ new_branch = params[:project].delete(:default_branch)
+
+ if project.repository.exists? && new_branch && new_branch != project.default_branch
+ project.change_head(new_branch)
+ end
+
+ project.update_attributes(params[:project], as: role)
+ end
+ end
+end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
new file mode 100644
index 0000000000..8a1fce17ce
--- /dev/null
+++ b/app/services/search/global_service.rb
@@ -0,0 +1,35 @@
+module Search
+ class GlobalService
+ attr_accessor :current_user, :params
+
+ def initialize(user, params)
+ @current_user, @params = user, params.dup
+ end
+
+ def execute
+ query = params[:search]
+ query = Shellwords.shellescape(query) if query.present?
+ return result unless query.present?
+
+ group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
+ projects = Project.accessible_to(current_user)
+ projects = projects.where(namespace_id: group.id) if group
+ project_ids = projects.pluck(:id)
+
+ result[:projects] = projects.search(query).limit(20)
+ result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20)
+ result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20)
+ result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size }
+ result
+ end
+
+ def result
+ @result ||= {
+ projects: [],
+ merge_requests: [],
+ issues: [],
+ total_results: 0,
+ }
+ end
+ end
+end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
new file mode 100644
index 0000000000..3ebaafc752
--- /dev/null
+++ b/app/services/search/project_service.rb
@@ -0,0 +1,37 @@
+module Search
+ class ProjectService
+ attr_accessor :project, :current_user, :params
+
+ def initialize(project, user, params)
+ @project, @current_user, @params = project, user, params.dup
+ end
+
+ def execute
+ query = params[:search]
+ query = Shellwords.shellescape(query) if query.present?
+ return result unless query.present?
+
+ if params[:search_code].present?
+ blobs = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
+ blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20)
+ result[:blobs] = blobs
+ result[:total_results] = blobs.total_count
+ else
+ result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20)
+ result[:issues] = project.issues.search(query).order('updated_at DESC').limit(20)
+ result[:total_results] = %w(issues merge_requests).sum { |items| result[items.to_sym].size }
+ end
+
+ result
+ end
+
+ def result
+ @result ||= {
+ merge_requests: [],
+ issues: [],
+ blobs: [],
+ total_results: 0,
+ }
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index de4fa9b79e..41014f199d 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -1,21 +1,21 @@
class SystemHooksService
- def self.execute_hooks_for(model, event)
+ def execute_hooks_for(model, event)
execute_hooks(build_event_data(model, event))
end
private
- def self.execute_hooks(data)
+ def execute_hooks(data)
SystemHook.all.each do |sh|
async_execute_hook sh, data
end
end
- def self.async_execute_hook(hook, data)
+ def async_execute_hook(hook, data)
Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data)
end
- def self.build_event_data(model, event)
+ def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
created_at: model.created_at
@@ -31,7 +31,8 @@ class SystemHooksService
path_with_namespace: model.path_with_namespace,
project_id: model.id,
owner_name: owner.name,
- owner_email: owner.respond_to?(:email) ? owner.email : nil
+ owner_email: owner.respond_to?(:email) ? owner.email : nil,
+ project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase
})
when User
data.merge!({
@@ -46,12 +47,13 @@ class SystemHooksService
project_id: model.project_id,
user_name: model.user.name,
user_email: model.user.email,
- project_access: model.human_access
+ project_access: model.human_access,
+ project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
})
end
end
- def self.build_event_name(model, event)
+ def build_event_name(model, event)
case model
when UsersProject
return "user_add_to_team" if event == :create
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
new file mode 100644
index 0000000000..17d86a7a27
--- /dev/null
+++ b/app/services/test_hook_service.rb
@@ -0,0 +1,6 @@
+class TestHookService
+ def execute(hook, current_user)
+ data = GitPushService.new.sample_data(hook.project, current_user)
+ hook.execute(data)
+ end
+end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 98794c9470..b122b6c865 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -3,6 +3,8 @@
class AttachmentUploader < CarrierWave::Uploader::Base
storage :file
+ after :store, :reset_events_cache
+
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
@@ -27,4 +29,8 @@ class AttachmentUploader < CarrierWave::Uploader::Base
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
+
+ def reset_events_cache(file)
+ model.reset_events_cache if model.is_a?(User)
+ end
end
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 431a9cbcd3..32f77cc1a7 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -1,11 +1,11 @@
%h3.page-title Background Jobs
-%p.light GitLab use #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
+%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
%hr
.ui-box
.title Sidekiq running processes
- .ui-box-body
+ .body
- if @sidekiq_processes.empty?
%h4.cred
%i.icon-warning-sign
@@ -14,27 +14,21 @@
%table.table
%thead
%th USER
- %th
%th PID
- %th
%th CPU
- %th
%th MEM
- %th
%th STATE
- %th
%th START
- %th
%th COMMAND
- %th
- - @sidekiq_processes.split("\n").each do |process|
+ %tbody
+ - @sidekiq_processes.each do |process|
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- - data = process.gsub!(/\s+/m, '|').strip.split('|')
+ - data = process.strip.split(' ')
%tr
- - 6.times do
+ %td= Settings.gitlab.user
+ - 5.times do
%td= data.shift
- %td
- %td= data.join(" ")
+ %td= data.join(' ')
.clearfix
%p
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
new file mode 100644
index 0000000000..c58ca2c9a3
--- /dev/null
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -0,0 +1,61 @@
+%h3.page-title
+ Broadcast Messages
+%p.light
+ Broadcast messages are displayed for every user and can be used to notify users about scheduled maintenance, recent upgrades and more.
+.broadcast-message-preview
+ %i.icon-bullhorn
+ %span Your message here
+
+= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal'} do |f|
+ -if @broadcast_message.errors.any?
+ .alert.alert-danger
+ - @broadcast_message.errors.full_messages.each do |msg|
+ %p= msg
+ .form-group
+ = f.label :message, class: 'control-label'
+ .col-sm-10
+ = f.text_area :message, class: "form-control", rows: 2, required: true
+ %div
+ = link_to '#', class: 'js-toggle-colors-link' do
+ Customize colors
+ .form-group.js-toggle-colors-container.hide
+ = f.label :color, "Background Color", class: 'control-label'
+ .col-sm-10
+ = f.text_field :color, placeholder: "#AA33EE", class: "form-control"
+ .light 6 character hex values starting with a # sign.
+ .form-group.js-toggle-colors-container.hide
+ = f.label :font, "Font Color", class: 'control-label'
+ .col-sm-10
+ = f.text_field :font, placeholder: "#224466", class: "form-control"
+ .light 6 character hex values starting with a # sign.
+ .form-group
+ = f.label :starts_at, class: 'control-label'
+ .col-sm-10.datetime-controls
+ = f.datetime_select :starts_at
+ .form-group
+ = f.label :ends_at, class: 'control-label'
+ .col-sm-10.datetime-controls
+ = f.datetime_select :ends_at
+ .form-actions
+ = f.submit "Add broadcast message", class: "btn btn-create"
+
+-if @broadcast_messages.any?
+ %ul.bordered-list.broadcast-messages
+ - @broadcast_messages.each do |broadcast_message|
+ %li
+ .pull-right
+ - if broadcast_message.starts_at
+ %strong
+ #{broadcast_message.starts_at.to_s(:short)}
+ \...
+ - if broadcast_message.ends_at
+ %strong
+ #{broadcast_message.ends_at.to_s(:short)}
+
+ = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-tiny' do
+ %i.icon-remove.cred
+
+ .message= broadcast_message.message
+
+
+ = paginate @broadcast_messages
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3064763b99..41760f8b1e 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -3,82 +3,141 @@
%p.light
You can manage projects, users and other GitLab data from here.
%hr
-.admin_dash.row
- .span4
- .light-well
- %h4 Projects
- .data
- = link_to admin_projects_path do
- %h1= Project.count
- %hr
- = link_to 'New Project', new_project_path, class: "btn btn-new"
- .span4
- .light-well
- %h4 Users
- .data
- = link_to admin_users_path do
- %h1= User.count
- %hr
- = link_to 'New User', new_admin_user_path, class: "btn btn-new"
- .span4
- .light-well
- %h4 Groups
- .data
- = link_to admin_groups_path do
- %h1= Group.count
- %hr
- = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
+.admin-dashboard
+ .row
+ .col-sm-4
+ .light-well
+ %h4 Projects
+ .data
+ = link_to admin_projects_path do
+ %h1= Project.count
+ %hr
+ = link_to 'New Project', new_project_path, class: "btn btn-new"
+ .col-sm-4
+ .light-well
+ %h4 Users
+ .data
+ = link_to admin_users_path do
+ %h1= User.count
+ %hr
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new"
+ .col-sm-4
+ .light-well
+ %h4 Groups
+ .data
+ = link_to admin_groups_path do
+ %h1= Group.count
+ %hr
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
-.row.prepend-top-10
- .span4
- %h4 Latest projects
- %hr
- - @projects.each do |project|
+ .row.prepend-top-10
+ .col-md-4
+ %h4 Latest projects
+ %hr
+ - @projects.each do |project|
+ %p
+ = link_to project.name_with_namespace, [:admin, project], class: 'str-truncated'
+ %span.light.pull-right
+ #{time_ago_with_tooltip(project.created_at)}
+
+ .col-md-4
+ %h4 Latest users
+ %hr
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated' do
+ = user.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(user.created_at)}
+
+ .col-md-4
+ %h4 Latest groups
+ %hr
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated' do
+ = group.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(group.created_at)}
+
+ %br
+ .row
+ .col-md-4
+ %h4 Stats
+ %hr
%p
- = link_to project.name_with_namespace, [:admin, project]
+ Forks
%span.light.pull-right
- = time_ago_in_words project.created_at
- ago
-
- .span4
- %h4 Latest users
- %hr
- - @users.each do |user|
+ = ForkedProjectLink.count
%p
- = link_to [:admin, user] do
- = user.name
+ Issues
%span.light.pull-right
- = time_ago_in_words user.created_at
- ago
+ = Issue.count
+ %p
+ Merge Requests
+ %span.light.pull-right
+ = MergeRequest.count
+ %p
+ Notes
+ %span.light.pull-right
+ = Note.count
+ %p
+ Snippets
+ %span.light.pull-right
+ = Snippet.count
+ %p
+ SSH Keys
+ %span.light.pull-right
+ = Key.count
+ %p
+ Milestones
+ %span.light.pull-right
+ = Milestone.count
+ %p
+ Monthly active users
+ %span.light.pull-right
+ = User.where("current_sign_in_at > ?", 30.days.ago).count
+ .col-md-4
+ %h4
+ Features
+ %hr
+ %p
+ Sign up
+ %span.light.pull-right
+ = boolean_to_icon gitlab_config.signup_enabled
+ %p
+ LDAP
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.ldap.enabled
+ %p
+ Gravatar
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.gravatar.enabled
+ %p
+ OmniAuth
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.omniauth.enabled
+ .col-md-4
+ %h4 Components
+ %hr
+ %p
+ GitLab
+ %span.pull-right
+ = Gitlab::VERSION
+ %p
+ GitLab Shell
+ %span.pull-right
+ = Gitlab::Shell.new.version
+ %p
+ GitLab API
+ %span.pull-right
+ = API::API::version
+ %p
+ Ruby
+ %span.pull-right
+ #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
- .span4
- %h4 Stats
- %hr
- %p
- Forks
- %span.light.pull-right
- = ForkedProjectLink.count
- %p
- Issues
- %span.light.pull-right
- = Issue.count
- %p
- Merge Requests
- %span.light.pull-right
- = MergeRequest.count
- %p
- Notes
- %span.light.pull-right
- = Note.count
- %p
- Snippets
- %span.light.pull-right
- = Snippet.count
- %p
- SSH Keys
- %span.light.pull-right
- = Key.count
- %p
- Milestones
- %span.light.pull-right
- = Milestone.count
+ %p
+ Rails
+ %span.pull-right
+ #{Rails::VERSION::STRING}
diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml
index 3918bf981a..eb5c91050a 100644
--- a/app/views/admin/groups/edit.html.haml
+++ b/app/views/admin/groups/edit.html.haml
@@ -1,25 +1,25 @@
%h3.page-title Edit Group
%hr
-= form_for [:admin, @group] do |f|
+= form_for [:admin, @group], html: { class: "form-horizontal" } do |f|
- if @group.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%span= @group.errors.full_messages.first
- .control-group.group_name_holder
- = f.label :name do
+ .form-group.group_name_holder
+ = f.label :name, class: 'control-label' do
Group name
- .controls
- = f.text_field :name, placeholder: "Example Group", class: "input-xxlarge"
+ .col-sm-10
+ = f.text_field :name, placeholder: "Example Group", class: "form-control"
- .control-group.group-description-holder
- = f.label :description, "Details"
- .controls
- = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+ .form-group.group-description-holder
+ = f.label :description, "Details", class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
- .control-group.group_name_holder
- = f.label :path do
+ .form-group.group_name_holder
+ = f.label :path, class: 'control-label' do
%span.cred Group path
- .controls
- = f.text_field :path, placeholder: "example-group", class: "input-xxlarge danger"
+ .col-sm-10
+ = f.text_field :path, placeholder: "example-group", class: "form-control danger"
%ul.cred
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index c9d7c24f20..9a0d596792 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,13 +1,15 @@
%h3.page-title
Groups (#{@groups.total_count})
- %small
- allows you to keep projects organized.
- Use groups for uniting related projects.
-
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
-%br
+
+%p.light
+ Group allows you to keep projects organized.
+ Use groups for uniting related projects.
+
+%hr
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
- = text_field_tag :name, params[:name], class: "span6"
+ .form-group
+ = text_field_tag :name, params[:name], class: "form-control input-mn-300"
= submit_tag "Search", class: "btn submit btn-primary"
%hr
@@ -18,28 +20,22 @@
.clearfix
.pull-right.prepend-top-10
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
- = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-small btn-remove"
%h4
= link_to [:admin, group] do
+ %i.icon-folder-close
= group.name
→
%span.monospace
- %i.icon-folder-close
%strong #{group.path}/
-
- .clearfix.light.append-bottom-10
- %span
- %b Members:
- %span.badge= group.members.size
- \|
- %span
- %b Projects:
- %span.badge= group.projects.count
-
.clearfix
%p
= truncate group.description, length: 150
+ .clearfix
+ %p.light
+ #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
+
= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml
index cfbe6b5ee5..ae0604cf98 100644
--- a/app/views/admin/groups/new.html.haml
+++ b/app/views/admin/groups/new.html.haml
@@ -1,27 +1,31 @@
%h3.page-title New Group
%hr
-= form_for [:admin, @group] do |f|
+= form_for [:admin, @group], html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%span= @group.errors.full_messages.first
- .control-group
- = f.label :name do
+ .form-group
+ = f.label :name, class: 'control-label' do
Group name
- .controls
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
- .control-group.group-description-holder
- = f.label :description, "Details"
- .controls
- = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+ .col-sm-10
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control"
+
+ .form-group.group-description-holder
+ = f.label :description, "Details", class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ %ul
+ %li A group is a collection of several projects
+ %li Groups are private by default
+ %li Members of a group may only view projects they have permission to access
+ %li Group project URLs are prefixed with the group namespace
+ %li Existing projects may be moved into a group
.form-actions
= f.submit 'Create group', class: "btn btn-create"
- %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 a 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 ca51a57000..6055865b4c 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -6,7 +6,7 @@
Edit
%hr
.row
- .span6
+ .col-md-6
.ui-box
.title
Group info:
@@ -39,14 +39,16 @@
%li
%strong
= link_to project.name_with_namespace, [:admin, project]
+ %span.label.label-gray
+ = repository_size(project)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
- .span6
+ .col-md-6
.ui-box
.title
Add user(s) to the group:
- .ui-box-body.form-holder
+ .body.form-holder
%p.light
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
@@ -55,7 +57,7 @@
%div
= users_select_tag(:user_ids, multiple: true)
%div.prepend-top-10
- = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select chosen"
+ = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select select2"
%hr
= submit_tag 'Add users into group', class: "btn btn-create"
.ui-box
@@ -68,9 +70,10 @@
- @group.users_groups.order('group_access DESC').each do |member|
- user = member.user
%li{class: dom_class(user)}
- %strong
- = link_to user.name, admin_user_path(user)
+ .list-item-name
+ %strong
+ = link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
- = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.icon-minus.icon-white
diff --git a/app/views/admin/hooks/_data_ex.html.erb b/app/views/admin/hooks/_data_ex.html.erb
deleted file mode 100644
index 5b16dfb398..0000000000
--- a/app/views/admin/hooks/_data_ex.html.erb
+++ /dev/null
@@ -1,71 +0,0 @@
-<% data_ex_str = <
-<% js_lexer = Pygments::Lexer[:js] %>
-<%= raw js_lexer.highlight(data_ex_str) %>
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 1bd7b8eac6..60773c2009 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- System Hooks
+ System hooks
%p.light
#{link_to "System hooks ", help_system_hooks_path, class: "vlink"} can be
@@ -8,35 +8,30 @@
%hr
-= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-inline' } do |f|
+= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
- .alert.alert-error
+ .alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
- .control-group
- = f.label :url, "URL:"
- .controls
- = f.text_field :url, class: "text_field input-xxlarge input-xpadding"
-
- = f.submit "Add System Hook", class: "btn btn-create"
+ .form-group
+ = f.label :url, "URL:", class: 'control-label'
+ .col-sm-10
+ = f.text_field :url, class: "form-control"
+ .form-actions
+ = f.submit "Add System Hook", class: "btn btn-create"
%hr
-if @hooks.any?
- %h3
- Hooks
- %small (#{@hooks.count})
- %br
- %table
- %tr
- %th URL
- %th Method
- %th
- - @hooks.each do |hook|
- %tr
- %td
- = link_to admin_hook_path(hook) do
- %strong= hook.url
- = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small pull-right"
- %td POST
- %td
- = link_to 'Remove', admin_hook_path(hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small pull-right"
+ .ui-box
+ .title
+ System hooks (#{@hooks.count})
+ %ul.well-list
+ - @hooks.each do |hook|
+ %li
+ .list-item-name
+ = link_to admin_hook_path(hook) do
+ %strong= hook.url
+
+ .pull-right
+ = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small"
+ = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index bc297209ae..51ad702154 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,55 +1,81 @@
.row
- .span4
+ .col-md-4
.admin-filter
- = form_tag admin_projects_path, method: :get, class: 'form-inline' do
- .control-group
- = label_tag :name, 'Name:', class: 'control-label'
- .controls
- = text_field_tag :name, params[:name], class: "span2"
-
- .control-group
- = label_tag :owner_id, 'Owner:', class: 'control-label'
- .controls
- = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large'
- .control-group
- = label_tag :public_only, 'Public Only', class: 'control-label'
- .controls
- = check_box_tag :public_only, 1, params[:public_only]
- .control-group
- = label_tag :with_push, 'Not empty', class: 'control-label'
- .controls
- = check_box_tag :with_push, 1, params[:with_push]
-
- %span.light Projects with push events
- .control-group
- = label_tag :abandoned, 'Abandoned', class: 'control-label'
- .controls
- = check_box_tag :abandoned, 1, params[:abandoned]
-
- %span.light No activity over 6 month
-
+ = form_tag admin_projects_path, method: :get, class: '' do
+ .form-group
+ = label_tag :name, 'Name:'
+ = text_field_tag :name, params[:name], class: "form-control"
+ .form-group
+ = label_tag :owner_id, 'Owner:'
+ %div
+ = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp'
+ .checkbox
+ = label_tag :with_push, 'Not empty'
+ = check_box_tag :with_push, 1, params[:with_push]
+
+ %span.light Projects with push events
+ .checkbox
+ = label_tag :abandoned, 'Abandoned'
+ = check_box_tag :abandoned, 1, params[:abandoned]
+
+ %span.light No activity over 6 month
+ %fieldset
+ %strong Visibility level:
+ .visibility-levels
+ - Project.visibility_levels.each do |label, level|
+ .checkbox
+ %label
+ = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
+ %span.descr
+ = visibility_level_icon(level)
+ = label
.form-actions
+ = hidden_field_tag :sort, params[:sort]
= submit_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_projects_path, class: "btn"
- .span8
+
+ .col-md-8
.ui-box
.title
Projects (#{@projects.total_count})
.pull-right
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to admin_projects_path(sort: nil) do
+ Name
+ = link_to admin_projects_path(sort: 'newest') do
+ Newest
+ = link_to admin_projects_path(sort: 'oldest') do
+ Oldest
+ = link_to admin_projects_path(sort: 'recently_updated') do
+ Recently updated
+ = link_to admin_projects_path(sort: 'last_updated') do
+ Last updated
+ = link_to admin_projects_path(sort: 'largest_repository') do
+ Largest repository
= link_to 'New Project', new_project_path, class: "btn btn-new"
%ul.well-list
- @projects.each do |project|
%li
- - if project.public
- = public_icon
- - else
- = private_icon
- = link_to project.name_with_namespace, [:admin, project]
+ .list-item-name
+ %span{ class: visibility_level_color(project.visibility_level) }
+ = visibility_level_icon(project.visibility_level)
+ = link_to project.name_with_namespace, [:admin, project]
.pull-right
+ %span.label.label-gray
+ = repository_size(project)
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Destroy', [project], confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove"
- if @projects.blank?
- %p.nothing_here_message 0 projects matches
+ .nothing-here-block 0 projects matches
= paginate @projects, theme: "gitlab"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 65b9911dc4..11f07743ac 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -5,7 +5,7 @@
Edit
%hr
.row
- .span6
+ .col-md-6
.ui-box
.title
Project info:
@@ -53,6 +53,11 @@
%strong
= @repository.path_to_repo
+ %li
+ %span.light Size
+ %strong
+ = repository_size(@project)
+
%li
%span.light last commit:
%strong
@@ -66,15 +71,26 @@
%li
%span.light access:
%strong
- - if @project.public
- %span.cblue
- %i.icon-share
- Public
- - else
- %span.cgreen
- %i.icon-lock
- Private
- .span6
+ %span{ class: visibility_level_color(@project.visibility_level) }
+ = visibility_level_icon(@project.visibility_level)
+ = visibility_level_label(@project.visibility_level)
+
+ .ui-box
+ .title
+ Transfer project
+ .body
+ = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
+ .form-group
+ = f.label :namespace_id, "Namespace", class: 'control-label'
+ .col-sm-10
+ = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
+
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = f.submit 'Transfer', class: 'btn btn-primary'
+
+ .col-md-6
- if @group
.ui-box
.title
@@ -100,12 +116,13 @@
- @project.users_projects.each do |users_project|
- user = users_project.user
%li.users_project
- %strong
- = link_to user.name, admin_user_path(user)
+ .list-item-name
+ %strong
+ = link_to user.name, admin_user_path(user)
.pull-right
- if users_project.owner?
%span.light Owner
- else
%span.light= users_project.human_access
- = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, remote: true, class: "btn btn-small btn-remove" do
+ = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do
%i.icon-remove
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 3f930c45fa..881a043f36 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -1,35 +1,35 @@
.user_new
- = form_for [:admin, @user] do |f|
+ = form_for [:admin, @user], html: { class: 'form-horizontal' } do |f|
-if @user.errors.any?
#error_explanation
- %ul.unstyled.alert.alert-error
+ %ul.unstyled.alert.alert-danger
- @user.errors.full_messages.each do |msg|
%li= msg
%fieldset
%legend Account
- .control-group
- = f.label :name
- .controls
- = f.text_field :name, required: true, autocomplete: "off"
+ .form-group
+ = f.label :name, class: 'control-label'
+ .col-sm-10
+ = f.text_field :name, required: true, autocomplete: "off", class: 'form-control'
%span.help-inline * required
- .control-group
- = f.label :username
- .controls
- = f.text_field :username, required: true, autocomplete: "off"
+ .form-group
+ = f.label :username, class: 'control-label'
+ .col-sm-10
+ = f.text_field :username, required: true, autocomplete: "off", class: 'form-control'
%span.help-inline * required
- .control-group
- = f.label :email
- .controls
- = f.text_field :email, required: true, autocomplete: "off"
+ .form-group
+ = f.label :email, class: 'control-label'
+ .col-sm-10
+ = f.text_field :email, required: true, autocomplete: "off", class: 'form-control'
%span.help-inline * required
- if @user.new_record?
%fieldset
%legend Password
- .control-group
- = f.label :password
- .controls
+ .form-group
+ = f.label :password, class: 'control-label'
+ .col-sm-10
%strong
A temporary password will be generated and sent to user.
%br
@@ -37,49 +37,52 @@
- else
%fieldset
%legend Password
- .control-group
- = f.label :password
- .controls= f.password_field :password, disabled: f.object.force_random_password
- .control-group
- = f.label :password_confirmation
- .controls= f.password_field :password_confirmation, disabled: f.object.force_random_password
+ .form-group
+ = f.label :password, class: 'control-label'
+ .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
+ .form-group
+ = f.label :password_confirmation, class: 'control-label'
+ .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
%fieldset
%legend Access
- .row
- .span8
- .control-group
- = f.label :projects_limit
- .controls= f.number_field :projects_limit
+ .form-group
+ = f.label :projects_limit, class: 'control-label'
+ .col-sm-10= f.number_field :projects_limit, class: 'form-control'
- .control-group
- = f.label :can_create_group
- .controls= f.check_box :can_create_group
+ .form-group
+ = f.label :can_create_group, class: 'control-label'
+ .col-sm-10= f.check_box :can_create_group
- .control-group
- = f.label :admin do
- %strong.cred Administrator
- .controls= f.check_box :admin
- .span4
- - unless @user.new_record?
- .alert.alert-error
- - if @user.blocked?
- %p This user is blocked and is not able to login to GitLab
- = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small"
- - else
- %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(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
+ .form-group
+ = f.label :admin, class: 'control-label'
+ - if current_user == @user
+ .col-sm-10= f.check_box :admin, disabled: true
+ .col-sm-10 You cannot remove your own admin rights
+ - else
+ .col-sm-10= f.check_box :admin
+ - unless @user.new_record? || current_user == @user
+ .alert.alert-danger
+ - if @user.blocked?
+ %p This user is blocked and is not able to login to GitLab
+ = link_to 'Unblock User', unblock_admin_user_path(@user), method: :put, class: "btn btn-small"
+ - else
+ %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(@user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove"
%fieldset
%legend Profile
- .control-group
- = f.label :skype
- .controls= f.text_field :skype
- .control-group
- = f.label :linkedin
- .controls= f.text_field :linkedin
- .control-group
- = f.label :twitter
- .controls= f.text_field :twitter
+ .form-group
+ = f.label :skype, class: 'control-label'
+ .col-sm-10= f.text_field :skype, class: 'form-control'
+ .form-group
+ = f.label :linkedin, class: 'control-label'
+ .col-sm-10= f.text_field :linkedin, class: 'form-control'
+ .form-group
+ = f.label :twitter, class: 'control-label'
+ .col-sm-10= f.text_field :twitter, class: 'form-control'
+ .form-group
+ = f.label :website_url, 'Website', class: 'control-label'
+ .col-sm-10= f.text_field :website_url, class: 'form-control'
.form-actions
- if @user.new_record?
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index b32f0ae87c..f42ae7c6a0 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,10 +1,6 @@
.row
- .span3
+ .col-md-3
.admin-filter
- = form_tag admin_users_path, method: :get, class: 'form-inline' do
- = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'search-text-input span2'
- = button_tag type: 'submit', class: 'btn' do
- %i.icon-search
%ul.nav.nav-pills.nav-stacked
%li{class: "#{'active' unless params[:filter]}"}
= link_to admin_users_path do
@@ -23,9 +19,15 @@
Without projects
%small.pull-right= User.without_projects.count
%hr
+ = form_tag admin_users_path, method: :get, class: 'form-inline' do
+ .form-group
+ = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
+ = button_tag type: 'submit', class: 'btn btn-primary' do
+ %i.icon-search
+ %hr
= link_to 'Reset', admin_users_path, class: "btn btn-cancel"
- .span9
+ .col-md-9
.ui-box
.title
Users (#{@users.total_count})
@@ -34,15 +36,16 @@
%ul.well-list
- @users.each do |user|
%li
- - if user.blocked?
- %i.icon-lock.cred
- - else
- %i.icon-user.cgreen
- = link_to user.name, [:admin, user]
- - if user.admin?
- %strong.cred (Admin)
- - if user == current_user
- %span.cred It's you!
+ .list-item-name
+ - if user.blocked?
+ %i.icon-lock.cred
+ - else
+ %i.icon-user.cgreen
+ = link_to user.name, [:admin, user]
+ - if user.admin?
+ %strong.cred (Admin)
+ - if user == current_user
+ %span.cred It's you!
.pull-right
%span.light
%i.icon-envelope
@@ -53,6 +56,6 @@
- if user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success"
- else
- = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
- = link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove"
+ = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-small btn-remove"
= paginate @users, theme: "gitlab"
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 655ed5f7f7..764b34499a 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -7,13 +7,13 @@
%span.cred (Admin)
.pull-right
- = link_to edit_admin_user_path(@user), class: "btn grouped" do
+ = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.icon-edit
Edit
%hr
.row
- .span6
+ .col-md-6
.ui-box
.title
Account:
@@ -43,6 +43,16 @@
%span.light Member since:
%strong
= @user.created_at.stamp("Nov 12, 2031")
+ - if @user.confirmed_at
+ %li
+ %span.light Confirmed at:
+ %strong
+ = @user.confirmed_at.stamp("Nov 12, 2031")
+ - else
+ %li
+ %span.light Confirmed:
+ %strong.cred
+ No
%li
%span.light Last sign-in at:
@@ -68,7 +78,6 @@
- if @user.blocked?
.alert.alert-info
%h4 This user is blocked
- %br
%p Blocking user has the following effects:
%ul
%li User will not be able to login
@@ -76,11 +85,11 @@
%li User will be removed from joined projects and groups
%li Personal projects will be left
%li Owned groups will be left
- = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", confirm: 'Are you sure?'
+ %br
+ = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' }
- else
- .alert
+ .alert.alert-warning
%h4 Block this user
- %br
%p Blocking user has the following effects:
%ul
%li User will not be able to login
@@ -88,12 +97,12 @@
%li User will be removed from joined projects and groups
%li Personal projects will be left
%li Owned groups will be left
- = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove"
+ %br
+ = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove"
- .alert.alert-error
+ .alert.alert-danger
%h4
Remove user
- %br
%p Deleting a user has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
@@ -104,9 +113,10 @@
%li
Next groups with all content will be removed:
%strong #{@user.solo_owned_groups.map(&:name).join(', ')}
- = link_to 'Remove user', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-remove"
+ %br
+ = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
- .span6
+ .col-md-6
- if @user.users_groups.present?
.ui-box
.title Groups:
@@ -114,11 +124,12 @@
- @user.users_groups.each do |user_group|
- group = user_group.group
%li.users_group
- %strong= link_to group.name, admin_group_path(group)
+ %span{class: ("list-item-name" unless user_group.owner?)}
+ %strong= link_to group.name, admin_group_path(group)
.pull-right
%span.light= user_group.human_access
- unless user_group.owner?
- = link_to group_users_group_path(group, user_group), confirm: remove_user_from_group_message(group, @user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ = link_to group_users_group_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.icon-remove.icon-white
.ui-box
@@ -138,7 +149,5 @@
%span.light= tm.human_access
- if tm.respond_to? :project
- = link_to project_team_member_path(project, @user), confirm: remove_from_project_team_message(project, @user), remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
+ = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
%i.icon-remove
-
-
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 8911772631..fdf96dd6f5 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -4,5 +4,6 @@
- if @events.any?
.content_list
- else
- %p.nothing_here_message Projects activity will be displayed here
-.loading.hide
+ .nothing-here-block Projects activity will be displayed here
+
+= spinner
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index b4f3866228..a6bc946bed 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -1,6 +1,6 @@
.ui-box
.title.clearfix
- = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter'
+ = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- if current_user.can_create_group?
%span.pull-right
= link_to new_group_path, class: "btn btn-new" do
@@ -10,10 +10,11 @@
- groups.each do |group|
%li.group-row
= link_to group_path(id: group.path), class: dom_class(group) do
+ = image_tag group_icon(group.path), class: "avatar s24"
%span.group-name.filter-title
= truncate(group.name, length: 35)
%span.arrow
%i.icon-angle-right
- if groups.blank?
%li
- %h3.nothing_here_message You have no groups yet.
+ .nothing-here-block You have no groups yet.
diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml
index 50b833f3d8..e326bee53a 100644
--- a/app/views/dashboard/_project.html.haml
+++ b/app/views/dashboard/_project.html.haml
@@ -1,12 +1,12 @@
= link_to project_path(project), class: dom_class(project) do
- %span.namespace-name
- - if project.namespace
- = project.namespace.human_name
- \/
- %span.project-name.filter-title
- = truncate(project.name, length: 25)
+ .dash-project-access-icon
+ = visibility_level_icon(project.visibility_level)
+ %span.str-truncated
+ %span.namespace-name
+ - if project.namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = project.name
%span.arrow
%i.icon-angle-right
- %span.last-activity
- %span Last activity:
- %span.date= project_last_activity(project)
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index b79b27fc95..44c7a4b8c8 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -1,6 +1,6 @@
.ui-box
.title.clearfix
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter'
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- if current_user.can_create_project?
%span.pull-right
= link_to new_project_path, class: "btn btn-new" do
@@ -14,7 +14,7 @@
- if projects.blank?
%li
- %h3.nothing_here_message There are no projects here.
+ .nothing-here-block There are no projects here.
- if @projects_count > @projects_limit
%li.bottom
%span.light
diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml
new file mode 100644
index 0000000000..8c9893ba84
--- /dev/null
+++ b/app/views/dashboard/_projects_filter.html.haml
@@ -0,0 +1,55 @@
+%fieldset
+ %ul.nav.nav-pills.nav-stacked
+ = nav_tab :scope, nil do
+ = link_to projects_dashboard_filter_path(scope: nil) do
+ All
+ %span.pull-right
+ = current_user.authorized_projects.count
+ = nav_tab :scope, 'personal' do
+ = link_to projects_dashboard_filter_path(scope: 'personal') do
+ Personal
+ %span.pull-right
+ = current_user.personal_projects.count
+ = nav_tab :scope, 'joined' do
+ = link_to projects_dashboard_filter_path(scope: 'joined') do
+ Joined
+ %span.pull-right
+ = current_user.authorized_projects.joined(current_user).count
+ = nav_tab :scope, 'owned' do
+ = link_to projects_dashboard_filter_path(scope: 'owned') do
+ Owned
+ %span.pull-right
+ = current_user.owned_projects.count
+
+%fieldset
+ %legend Visibility
+ %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter
+ - Gitlab::VisibilityLevel.values.each do |level|
+ %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(visibility_level: level) do
+ = visibility_level_icon(level)
+ = visibility_level_label(level)
+
+- if @groups.present?
+ %fieldset
+ %legend Groups
+ %ul.nav.nav-pills.nav-stacked.nav-small
+ - @groups.each do |group|
+ %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(group: group.name) do
+ %i.icon-folder-close-alt
+ = group.name
+ %small.pull-right
+ = group.projects.count
+
+
+
+- if @labels.present?
+ %fieldset
+ %legend Labels
+ %ul.nav.nav-pills.nav-stacked.nav-small
+ - @labels.each do |label|
+ %li{ class: (label.name == params[:label]) ? 'active' : 'light' }
+ = link_to projects_dashboard_filter_path(scope: params[:scope], label: label.name) do
+ %i.icon-tag
+ = label.name
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 79d5dca884..e0993293ea 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -29,8 +29,23 @@
%p.slead
You can create a group for several dependent projects.
%br
- Group is the best way to manage projects and members
+ Groups are the best way to manage projects and members
.link_holder
= link_to new_group_path, class: "btn btn-new" do
New group »
+-if @publicish_project_count > 0
+ %hr
+ %div
+ .dashboard-intro-icon
+ %i.icon-globe
+ %div
+ %p.slead
+ There are
+ %strong= @publicish_project_count
+ public projects on this server.
+ %br
+ Public projects are an easy way to allow everyone to have read-only access.
+ .link_holder
+ = link_to public_projects_path, class: "btn btn-new" do
+ Browse public projects »
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
old mode 100644
new mode 100755
index 82880d5ef6..af627588ad
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,13 +1,13 @@
%h3.page-title
- Issues assigned to me
+ Issues
%span.pull-right #{@issues.total_count} issues
%p.light
- For all issues you should visit the project's issues page, or use the search panel to find a specific issue.
+ List all issues from all projects you have access to.
%hr
.row
- .span3
+ .col-md-3
= render 'shared/filter', entity: 'issue'
- .span9
+ .col-md-9
= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
old mode 100644
new mode 100755
index 9c96edeefd..550a93e178
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -4,10 +4,10 @@
%p.light
- Only merge requests created by you or assigned to you are listed here.
+ List all merge requests from all projects you have access to.
%hr
.row
- .span3
+ .col-md-3
= render 'shared/filter', entity: 'merge_request'
- .span9
+ .col-md-9
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 785eb61333..e1c9a5941e 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -1,67 +1,47 @@
-%h3.page-title My Projects
+%h3.page-title
+ My Projects
+.pull-right
+ .dropdown.inline
+ %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to projects_dashboard_filter_path(sort: nil) do
+ Name
+ = link_to projects_dashboard_filter_path(sort: 'newest') do
+ Newest
+ = link_to projects_dashboard_filter_path(sort: 'oldest') do
+ Oldest
+ = link_to projects_dashboard_filter_path(sort: 'recently_updated') do
+ Recently updated
+ = link_to projects_dashboard_filter_path(sort: 'last_updated') do
+ Last updated
%p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr
.row
- .span3
- %ul.nav.nav-pills.nav-stacked
- = nav_tab :scope, nil do
- = link_to projects_dashboard_path do
- All
- %span.pull-right
- = current_user.authorized_projects.count
- = nav_tab :scope, 'personal' do
- = link_to projects_dashboard_path(scope: 'personal') do
- Personal
- %span.pull-right
- = current_user.personal_projects.count
- = nav_tab :scope, 'joined' do
- = link_to projects_dashboard_path(scope: 'joined') do
- Joined
- %span.pull-right
- = current_user.authorized_projects.joined(current_user).count
- = nav_tab :scope, 'owned' do
- = link_to projects_dashboard_path(scope: 'owned') do
- Owned
- %span.pull-right
- = current_user.owned_projects.count
-
-
- - if @groups.present?
- %fieldset
- %legend Groups
- %ul.bordered-list
- - @groups.each do |group|
- %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
- = link_to projects_dashboard_path(group: group.name) do
- %i.icon-folder-close-alt
- = group.name
- %small.pull-right
- = group.projects.count
-
-
-
- - if @labels.present?
- %fieldset
- %legend Labels
- %ul.bordered-list
- - @labels.each do |label|
- %li{ class: (label.name == params[:label]) ? 'active' : 'light' }
- = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do
- %i.icon-tag
- = label.name
-
- .span9
+ .col-md-3.hidden-sm.hidden-xs.side-filters
+ = render "projects_filter"
+ .col-md-9
%ul.bordered-list.my-projects.top-list
- @projects.each do |project|
- %li
+ %li.my-project-row
%h4.project-title
+ .project-access-icon
+ = visibility_level_icon(project.visibility_level)
= link_to project_path(project), class: dom_class(project) do
= project.name_with_namespace
- - if project.public
- %small.access-icon
- = public_icon
- Public
+
+ - if current_user.can_leave_project?(project)
+ .pull-right
+ = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
+ %i.icon-signout
+ Leave
- if project.forked_from_project
%small.pull-right
@@ -70,6 +50,10 @@
= link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
.project-info
.pull-right
+ - if project.archived?
+ %span.label
+ %i.icon-archive
+ Archived
- project.labels.each do |label|
%span.label.label-info
%i.icon-tag
@@ -80,9 +64,10 @@
%span.light Last activity:
%span.date= project_last_activity(project)
+
- if @projects.blank?
%li
- %h3.nothing_here_message There are no projects here.
+ .nothing-here-block There are no projects here.
.bottom
= paginate @projects, theme: "gitlab"
diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml
index 2305eae1f7..e5b7fbf097 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -1,8 +1,8 @@
- if @has_authorized_projects
- .dashboard
- .activities.span8
+ .dashboard.row
+ .activities.col-md-8
= render 'activities'
- .side.span4
+ .side.col-md-4.hidden-sm.hidden-xs
= render 'sidebar'
- else
diff --git a/app/views/dashboard/show.js.haml b/app/views/dashboard/show.js.haml
deleted file mode 100644
index 7e5a148e5e..0000000000
--- a/app/views/dashboard/show.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
deleted file mode 100644
index adc9b67209..0000000000
--- a/app/views/devise/confirmations/new.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-Resend confirmation instructions
-
-<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
- <%= devise_error_messages! %>
-
- <%= f.label :email %>
- <%= f.email_field :email %>
-
- <%= f.submit "Resend confirmation instructions" %>
-<% end %>
-
-<%= render partial: "devise/shared/links" %>
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
new file mode 100755
index 0000000000..bf634d9de6
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.haml
@@ -0,0 +1,15 @@
+.login-box
+ %h3.page-title Resend confirmation instructions
+ = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
+ .devise-errors
+ = devise_error_messages!
+ .clearfix.append-bottom-20
+ = f.email_field :email, placeholder: 'Email', class: "form-control", required: true
+ .clearfix.append-bottom-10
+ = f.submit "Resend confirmation instructions", class: 'btn btn-success'
+ %hr
+ %p
+ %span.light
+ Already have login and password?
+ %strong
+ = link_to "Sign in", new_session_path(resource_name)
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
index 7b4fd52696..553d08369e 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.erb
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -1,5 +1,9 @@
-Welcome <%= @resource.email %>!
+Welcome <%= @resource.name %>!
-You can confirm your account through the link below:
+<% if @resource.unconfirmed_email.present? %>
+ You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:
+<% else %>
+ You can confirm your account through the link below:
+<% end %>
<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @resource.confirmation_token) %>
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 3e4a4a0f1c..95c52608e1 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -1,11 +1,12 @@
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "login-box" }) do |f|
%h3 Change your password
- = devise_error_messages!
+ .devise-errors
+ = devise_error_messages!
= f.hidden_field :reset_password_token
%div
- = f.password_field :password, class: "text top", placeholder: "New password"
+ = f.password_field :password, class: "form-control top", placeholder: "New password", required: true
%div
- = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm new password"
+ = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
%div
.clearfix.append-bottom-10
= f.submit "Change my password", class: "btn btn-primary"
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
old mode 100644
new mode 100755
index 3df65d037a..040821ca32
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -1,10 +1,14 @@
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: "login-box", method: :post }) do |f|
%h3.page-title Reset password
- = devise_error_messages!
- = f.email_field :email, placeholder: "Email", class: "text"
- %br/
- %br/
- = f.submit "Reset password", class: "btn-primary btn"
- .pull-right
- = link_to "Sign in", new_session_path(resource_name), class: "btn"
- %br/
+ .devise-errors
+ = devise_error_messages!
+ .clearfix.append-bottom-20
+ = f.email_field :email, placeholder: "Email", class: "form-control", required: true
+ .clearfix.append-bottom-10
+ = f.submit "Reset password", class: "btn-primary btn"
+ %hr
+ %p
+ %span.light
+ Already have login and password?
+ %strong
+ = link_to "Sign in", new_session_path(resource_name)
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index 139acf28a9..b11817af95 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -23,6 +23,6 @@
Cancel my account
-Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), confirm: "Are you sure?", method: :delete %>.
+Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.
<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index d749d7bac0..24bc040654 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -1,19 +1,24 @@
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "login-box" }) do |f|
%h3.page-title Sign Up
- %br
- = devise_error_messages!
+ .devise-errors
+ = devise_error_messages!
%div
- = f.text_field :name, class: "text top", placeholder: "Name", required: true
+ = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
%div
- = f.text_field :username, class: "text middle", placeholder: "Username", required: true
+ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
- = f.email_field :email, class: "text middle", placeholder: "Email", required: true
+ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
%div
- = f.password_field :password, class: "text middle", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true
%div
- = f.password_field :password_confirmation, class: "text bottom", placeholder: "Confirm password", required: true
+ = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
%div
= f.submit "Sign up", class: "btn-create btn"
%hr
- = link_to "Sign in", new_session_path(resource_name)
- = link_to "Forgot your password?", new_password_path(resource_name), class: "pull-right"
+ %p
+ %span.light
+ Have an account?
+ %strong
+ = link_to "Sign in", new_session_path(resource_name)
+ %p
+ = link_to "Forgot your password?", new_password_path(resource_name)
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 0c8be9d5c4..a2f85fa3fe 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,6 +1,6 @@
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
- = f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus"
- = f.password_field :password, class: "text bottom", placeholder: "Password"
+ = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
+ = f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
.clearfix.append-bottom-10
%label.checkbox.remember_me{for: "user_remember_me"}
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 575d33949b..bb1d0a4001 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,5 +1,5 @@
= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
- = text_field_tag :username, nil, {class: "text top", placeholder: "LDAP Login", autofocus: "autofocus"}
- = password_field_tag :password, nil, {class: "text bottom", placeholder: "Password"}
+ = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
+ = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
= submit_tag "LDAP Sign in", class: "btn-create btn"
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index 15854effbf..31221ae9c3 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,6 +1,6 @@
.login-box
%h3.page-title Sign in
- - if ldap_enabled?
+ - if ldap_enabled? && gitlab_config.signin_enabled
%ul.nav.nav-tabs
%li.active
= link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
@@ -12,19 +12,32 @@
%div#tab-signin.tab-pane
= render partial: 'devise/sessions/new_base'
- - else
+ - elsif ldap_enabled?
+ = render partial: 'devise/sessions/new_ldap'
+
+ - elsif gitlab_config.signin_enabled
= render partial: 'devise/sessions/new_base'
+ - else
+ %div
+ No authentication methods configured.
- = render 'devise/sessions/oauth_providers' if devise_mapping.omniauthable?
+
+ = render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ %hr
- if gitlab_config.signup_enabled
- %hr
- %div
- Don't have an account?
+ %p
+ %span.light
+ Don't have an account?
%strong
= link_to "Sign up", new_registration_path(resource_name)
+ %p
+ %span.light Did not receive confirmation email?
+ = link_to "Send again", new_confirmation_path(resource_name)
+
+
- if extra_config.has_key?('sign_in_text')
%hr
= markdown(extra_config.sign_in_text)
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 892dacafa6..8cf26671e3 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,10 +1,10 @@
- if event.proper?
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
%span.cgray.pull-right
- #{time_ago_in_words(event.created_at)} ago.
+ #{time_ago_with_tooltip(event.created_at)}
= cache event do
- = image_tag avatar_icon(event.author_email), class: "avatar s24", alt:''
+ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index de5634d3c5..6db05a1a5a 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -5,9 +5,8 @@
%strong= truncate(event.ref_name, length: 28)
at
%strong= link_to_project event.project
- %span
- = time_ago_in_words(event.created_at)
- ago.
+ #{time_ago_with_tooltip(event.created_at)}
+
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do
Create Merge Request
diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml
new file mode 100644
index 0000000000..3d62d47886
--- /dev/null
+++ b/app/views/events/_events.html.haml
@@ -0,0 +1 @@
+= render @events
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index db5f3ebb00..ad2afbce14 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -14,8 +14,8 @@
- note = event.target
- if note.attachment.url
- if note.attachment.image?
- = link_to note.attachment.url, target: '_blank' do
- = image_tag note.attachment.url, class: 'note-image-attach'
+ = link_to note.attachment.secure_url, target: '_blank' do
+ = image_tag note.attachment.secure_url, class: 'note-image-attach'
- else
= link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
%i.icon-paper-clip
diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml
index 9fbc6c190c..fe8c0669c0 100644
--- a/app/views/groups/_filter.html.haml
+++ b/app/views/groups/_filter.html.haml
@@ -21,7 +21,7 @@
= project.name_with_namespace
%small.pull-right= entities_per_project(project, entity)
- if @projects.blank?
- %p.nothing_here_message This group has no projects yet
+ .nothing-here-block This group has no projects yet
%fieldset
%hr
diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml
index 234392c03e..3ab9276c54 100644
--- a/app/views/groups/_new_group_member.html.haml
+++ b/app/views/groups/_new_group_member.html.haml
@@ -1,20 +1,11 @@
-= form_for @users_group, url: group_users_groups_path(@group) do |f|
- %fieldset
- %legend
- New member(s) for
- %strong #{@group.name}
- group
+= form_for @users_group, url: group_users_groups_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
+ .form-group
+ = f.label :user_ids, "People", class: 'control-label'
+ .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
- %p 1. Choose users you want in the group
- .control-group
- = f.label :user_ids, "People"
- .controls= users_select_tag(:user_ids, multiple: true, class: 'input-large')
-
- %p 2. Set access level for them
- .control-group
- = f.label :group_access, "Group Access"
- .controls= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select chosen"
-
- .form-actions
- = f.submit 'Add users into group', class: "btn btn-create"
+ .form-group
+ = f.label :group_access, "Group Access", class: 'control-label'
+ .col-sm-10= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select select2"
+ .form-actions
+ = f.submit 'Add users into group', class: "btn btn-create"
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 16a3c60f66..bd4e3156af 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -8,14 +8,14 @@
New project
%ul.well-list
- if projects.blank?
- %p.nothing_here_message This groups has no projects yet
+ .nothing-here-block This groups has no projects yet
- projects.each do |project|
%li.project-row
= link_to project_path(project), class: dom_class(project) do
- %span.project-name
- = truncate(project.name, length: 25)
+ .dash-project-access-icon
+ = visibility_level_icon(project.visibility_level)
+ %span.str-truncated
+ %span.project-name
+ = truncate(project.name, length: 25)
%span.arrow
%i.icon-angle-right
- %span.last-activity
- %span Last activity:
- %span.date= project_last_activity(project)
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 10b974ea22..500c37ab71 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,18 +1,20 @@
.row
- .span2
+ .col-md-2
%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
%li.active
= link_to '#tab-edit', 'data-toggle' => 'tab' do
%i.icon-edit
- Edit Group
+ Group
%li
= link_to '#tab-projects', 'data-toggle' => 'tab' do
%i.icon-folder-close
Projects
%li
- = link_to 'Remove', '#tab-remove', 'data-toggle' => 'tab'
+ = link_to '#tab-remove', 'data-toggle' => 'tab' do
+ %i.icon-remove-sign
+ Remove
- .span10
+ .col-md-10
.tab-content
.tab-pane.active#tab-edit
.ui-box
@@ -20,20 +22,40 @@
%strong= @group.name
group settings:
%div.form-holder
- = form_for @group do |f|
+ = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%span= @group.errors.full_messages.first
- .control-group
- = f.label :name do
+ .form-group
+ = f.label :name, class: 'control-label' do
Group name
- .controls
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
+ .col-sm-10
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
- .control-group.group-description-holder
- = f.label :description, "Details"
- .controls
- = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+ .form-group.group-description-holder
+ = f.label :description, "Details", class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160'
+ %p.light
+ - if @group.avatar?
+ You can change your group avatar here
+ - else
+ You can upload an group avatar here
+ %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
+ %i.icon-paper-clip
+ %span Choose File ...
+
+ %span.file_name.js-avatar-filename File name...
+ = f.file_field :avatar, class: "js-group-avatar-input hidden"
+ .light The maximum file size allowed is 100KB.
+ - if @group.avatar?
+ %hr
+ = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
.form-actions
= f.submit 'Save group', class: "btn btn-save"
@@ -51,25 +73,23 @@
%ul.well-list
- @group.projects.each do |project|
%li
- - if project.public
- = public_icon
- - else
- = private_icon
- = link_to project.name_with_namespace, project
+ .list-item-name
+ = visibility_level_icon(project.visibility_level)
+ = link_to project.name_with_namespace, project
.pull-right
= link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Remove', project, confirm: remove_project_message(project), method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove"
- if @group.projects.blank?
- %p.nothing_here_message This group has no projects yet
+ .nothing-here-block This group has no projects yet
.tab-pane#tab-remove
.ui-box.ui-box-danger
.title Remove group
- .ui-box-body
+ .body
%p
Removing group will cause all child projects and resources to be removed.
%p
%strong Removed group can not be restored!
- = link_to 'Remove Group', @group, confirm: 'Removed group can not be restored! Are you sure?', method: :delete, class: "btn btn-remove"
+ = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 482613f172..4b11d91dc9 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,15 +1,17 @@
%h3.page-title
- Issues assigned to me
+ Issues
%span.pull-right #{@issues.total_count} issues
%p.light
Only issues from
%strong #{@group.name}
- group are listed here. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
+ group are listed here.
+ - if current_user
+ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
%hr
.row
- .span3
+ .col-md-3
= render 'shared/filter', entity: 'issue'
- .span9
+ .col-md-9
= render 'shared/issues'
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
index 124560e478..fa44ee2ae1 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/members.html.haml
@@ -1,20 +1,41 @@
+- show_roles = should_user_see_group_roles?(current_user, @group)
%h3.page-title
Group members
-%p.light
- Members of group have access to all group projects.
- Read more about permissions
- %strong= link_to "here", help_permissions_path, class: "vlink"
+- if show_roles
+ %p.light
+ Members of group have access to all group projects.
+ Read more about permissions
+ %strong= link_to "here", help_permissions_path, class: "vlink"
%hr
-- can_manage_group = current_user.can? :manage_group, @group
-.ui-box
+
+.clearfix.js-toggle-container
+ = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
+ = submit_tag 'Search', class: 'btn'
+
+ - if current_user && current_user.can?(:manage_group, @group)
+ .pull-right
+ = link_to '#', class: 'btn btn-new js-toggle-button' do
+ Add members
+ %i.icon-chevron-down
+
+ .js-toggle-content.hide.new-group-member-holder
+ = render "new_group_member"
+
+.ui-box.prepend-top-20
.title
%strong #{@group.name}
group members
%small
- (#{@members.count})
+ (#{@members.total_count})
%ul.well-list
- @members.each do |member|
- = render 'users_groups/users_group', member: member, show_controls: can_manage_group
-- if can_manage_group
- = render "new_group_member"
+ = render 'users_groups/users_group', member: member, show_roles: show_roles, show_controls: true
+= paginate @members, theme: 'gitlab'
+
+:coffeescript
+ $('form.member-search-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '?' + $(@).serialize()
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 8a9b03535b..209130ec44 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -3,12 +3,14 @@
%span.pull-right #{@merge_requests.total_count} merge requests
%p.light
- Authored or assigned to you from
+ Only merge requests from
%strong #{@group.name}
- group. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+ group are listed here.
+ - if current_user
+ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
.row
- .span3
+ .col-md-3
= render 'shared/filter', entity: 'merge_request'
- .span9
+ .col-md-9
= render 'shared/merge_requests'
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index d6d514e9b1..955a107e54 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,20 +1,21 @@
-= form_for @group do |f|
+= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%span= @group.errors.full_messages.first
- .control-group
- = f.label :name do
+ .form-group
+ = f.label :name, class: 'control-label' do
Group name
- .controls
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "input-xxlarge left"
+ .col-sm-10
+ = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control"
- .control-group.group-description-holder
- = f.label :description, "Details"
- .controls
- = f.text_area :description, maxlength: 250, class: "input-xxlarge js-gfm-input", rows: 4
+ .form-group.group-description-holder
+ = f.label :description, "Details", class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
- .control-group
- .controls
+ .form-group
+ .col-sm-2
+ .col-sm-10
%ul
%li A group is a collection of several projects
%li Groups are private by default
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index e613ed3eaa..0c62772a5c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,27 +1,33 @@
.dashboard
- .activities.span8
- = render "events/event_last_push", event: @last_push
- = link_to dashboard_path, class: 'btn btn-tiny' do
- ← To dashboard
-
+ .activities.col-md-8.hidden-sm.hidden-xs
+ - if current_user
+ = render "events/event_last_push", event: @last_push
+ = link_to dashboard_path, class: 'btn btn-tiny' do
+ ← To dashboard
+
%span.cgray You will only see events from projects in this group
%hr
= render 'shared/event_filter'
- if @events.any?
.content_list
- else
- %p.nothing_here_message Project activity will be displayed here
- .loading.hide
- .side.span4
- - if @group.description.present?
- .description-block
- = @group.description
+ .nothing-here-block Project activity will be displayed here
+ = spinner
+ .side.col-md-4
+ .light-well.append-bottom-20
+ = image_tag group_icon(@group.path), class: "avatar s90"
+ .clearfix.light
+ %h3.page-title
+ = @group.name
+ - if @group.description.present?
+ %p= @group.description
= render "projects", projects: @projects
- .prepend-top-20
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
- %strong
- %i.icon-rss
- News Feed
+ - if current_user
+ .prepend-top-20
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
+ %strong
+ %i.icon-rss
+ News Feed
%hr
= render 'shared/promo'
diff --git a/app/views/groups/show.js.haml b/app/views/groups/show.js.haml
deleted file mode 100644
index 7e5a148e5e..0000000000
--- a/app/views/groups/show.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml
index 502cc31a80..af723f906d 100644
--- a/app/views/help/_api_layout.html.haml
+++ b/app/views/help/_api_layout.html.haml
@@ -1,13 +1,13 @@
.row
- .span3
+ .col-md-3
.append-bottom-20
= link_to help_path, class: 'btn btn-small' do
%i.icon-angle-left
Back to help
%ul.nav.nav-pills.nav-stacked
- - %w(README projects project_snippets repositories deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file|
+ - %w(README projects project_snippets repositories repository_files commits deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file|
%li{class: file == @category ? 'active' : nil}
= link_to file.titleize, help_api_file_path(file)
- .span9.pull-right
+ .col-md-9.pull-right
= yield
diff --git a/app/views/help/_layout.html.haml b/app/views/help/_layout.html.haml
index da917888ee..201d63ca24 100644
--- a/app/views/help/_layout.html.haml
+++ b/app/views/help/_layout.html.haml
@@ -1,37 +1,12 @@
.row
- .span3{:"data-spy" => 'affix'}
- .ui-box
- .title
- Help
- %ul.well-list
- %li
- %strong= link_to "Workflow", help_workflow_path
- %li
- %strong= link_to "SSH keys", help_ssh_path
+ .col-md-3
+ %h3.page-title Help
+ %ul.nav.nav-pills.nav-stacked
+ - links = {:"Workflow" => help_workflow_path, :"SSH Keys" => help_ssh_path, :"GitLab Markdown" => help_markdown_path, :"Permissions" => help_permissions_path, :"API" => help_api_path, :"Web Hooks" => help_web_hooks_path, :"Rake Tasks" => help_raketasks_path, :"System Hooks" => help_system_hooks_path, :"Public Access" => help_public_access_path, :"Security" => help_security_path}
+ - links.each do |title,path|
+ %li{class: current_page?(path) ? 'active' : nil}
+ = link_to title, path
- %li
- %strong= link_to "GitLab Markdown", help_markdown_path
-
- %li
- %strong= link_to "Permissions", help_permissions_path
-
- %li
- %strong= link_to "API", help_api_path
-
- %li
- %strong= link_to "Web Hooks", help_web_hooks_path
-
- %li
- %strong= link_to "Rake Tasks", help_raketasks_path
-
- %li
- %strong= link_to "System Hooks", help_system_hooks_path
-
- %li
- %strong= link_to "Public Access", help_public_access_path
-
- %li
- %strong= link_to "Security", help_security_path
-
- .span9.pull-right
- = yield
+ .col-md-9
+ .wiki
+ = yield
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index e979e7c0d0..500e5dc65e 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -1,30 +1,32 @@
-#modal-shortcuts.modal.hide
- .modal-header
- %a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3 Keyboard Shortcuts
- .modal-body
- %h5 Global Shortcuts
- %p
- %span.label.label-inverse s
- –
- Focus Search
- %p
- %span.label.label-inverse ?
- –
- Show this dialog
+#modal-shortcuts.modal.hide{tabindex: -1}
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Keyboard Shortcuts
+ .modal-body
+ %h5 Global Shortcuts
+ %p
+ %span.label.label-inverse s
+ –
+ Focus Search
+ %p
+ %span.label.label-inverse ?
+ –
+ Show this dialog
- %h5 Project Files browsing
- %p
- %span.label.label-inverse
- %i.icon-arrow-up
- –
- Move selection up
- %p
- %span.label.label-inverse
- %i.icon-arrow-down
- –
- Move selection down
- %p
- %span.label.label-inverse Enter
- –
- Open selection
+ %h5 Project Files browsing
+ %p
+ %span.label.label-inverse
+ %i.icon-arrow-up
+ –
+ Move selection up
+ %p
+ %span.label.label-inverse
+ %i.icon-arrow-down
+ –
+ Move selection down
+ %p
+ %span.label.label-inverse Enter
+ –
+ Open selection
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 869f87df4d..724fe1d6b3 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,4 +1,4 @@
-.hero-unit
+.jumbotron
%h2
GitLab
%span= Gitlab::VERSION
@@ -6,19 +6,25 @@
%p.slead
GitLab is open source software to collaborate on code.
%br
- Create projects and repositories, manage access and do code reviews.
+ Manage git repositories with fine grained access controls that keep your code secure.
%br
- Read more about GitLab at #{link_to "gitlab.org", "http://gitlab.org/", target: "_blank"}.
+ Perform code reviews and enhance collaboration with merge requests.
+ %br
+ Each project can also have an issue tracker and a wiki.
+ %br
+ Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
+ %br
+ Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}.
.row
- .span4
+ .col-md-4
.ui-box
.title
Quick help
%ul.well-list
%li
- Email your
- = mail_to gitlab_config.support_email, "support contact"
+ See our website for
+ = link_to "getting help", "https://www.gitlab.com/getting-help/"
%li
Use the
= link_to "search bar", '#', onclick: "$('#search').focus();"
@@ -26,16 +32,8 @@
%li
Use
= link_to "shortcuts", '#', onclick: "new Shortcuts()"
- %li
- Ask in our
- = link_to "mailing list", "https://groups.google.com/forum/#!forum/gitlabhq"
- or on
- = link_to "Stack Overflow", "http://stackoverflow.com/questions/tagged/gitlab"
- %li
- Browse our
- = link_to "issue tracker", "https://github.com/gitlabhq/gitlabhq/issues"
- .span4
+ .col-md-4
.ui-box
.title
User documentation
@@ -64,7 +62,7 @@
%strong= link_to "Web Hooks", help_web_hooks_path
%p Let GitLab notify you when new code has been pushed to your project.
- .span4
+ .col-md-4
.ui-box
.title
Admin documentation
diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml
index df35f41fc9..5d3ee4526f 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -1,212 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Permissions
- %p.light Users have different abilities depending on the access level they have in particular group or project.
- %p.light If a user is both in a project group and in the project itself the highest permission level is used.
- %p.light If a user is a GitLab administrator they receive all permissions.
- %hr
+ %h3.page-title Permissions
- %h4 Project:
- %table.table
- %thead
- %tr
- %th Action
- %th Guest
- %th Reporter
- %th Developer
- %th Master
- %th Owner
- %tbody
- %tr
- %td Create new issue
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Leave comments
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Write on project wall
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Pull project code
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Download project
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Create code snippets
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Create new merge request
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Create new branches
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Push to non-protected branches
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Remove non-protected branches
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Add tags
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Write a wiki
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Add new team members
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Push to protected branches
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Remove protected branches
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Edit project
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Add Deploy Keys to project
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Configure Project Hooks
- %td
- %td
- %td
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Switch public mode
- %td
- %td
- %td
- %td
- %td.permission-x ✓
- %tr
- %td Transfer project to another namespace
- %td
- %td
- %td
- %td
- %td.permission-x ✓
- %tr
- %td Remove project
- %td
- %td
- %td
- %td
- %td.permission-x ✓
-
- %h4 Group
- %table.table
- %thead
- %tr
- %th Action
- %th Guest
- %th Reporter
- %th Developer
- %th Master
- %th Owner
- %tbody
- %tr
- %td Browse group
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %td.permission-x ✓
- %tr
- %td Edit group
- %td
- %td
- %td
- %td
- %td.permission-x ✓
- %tr
- %td Create project in group
- %td
- %td
- %td
- %td
- %td.permission-x ✓
- %tr
- %td Manage group members
- %td
- %td
- %td
- %td
- %td.permission-x ✓
- %tr
- %td Remove group
- %td
- %td
- %td
- %td
- %td.permission-x ✓
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "permissions", "permissions.md"))
diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml
index c67402ee31..d1ec3f5d5c 100644
--- a/app/views/help/public_access.html.haml
+++ b/app/views/help/public_access.html.haml
@@ -1,15 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Public Access
-
- %p
- GitLab allows you to open selected projects to be accessed publicly.
- These projects will be cloneable
- %em without any
- authentication.
- Also they will be listed on the #{link_to "public access directory", public_root_path}.
-
- %ol
- %li Go to your project dashboard
- %li Click on the "Edit" tab
- %li Select "Public clone access"
+ %h3.page-title Public Access
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "public_access", "public_access.md"))
diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml
index 72a21b89ca..75419c3dd0 100644
--- a/app/views/help/ssh.html.haml
+++ b/app/views/help/ssh.html.haml
@@ -1,22 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title SSH Keys
+ %h3.page-title SSH Keys
- %p.slead
- SSH key allows you to establish a secure connection between your computer and GitLab
-
- %p.slead
- To generate a new SSH key just open your terminal and use code below.
-
- %pre.dark
- ssh-keygen -t rsa -C "#{current_user.email}"
-
- \# Creates a new ssh key using the provided email
- \# Generating public/private rsa key pair...
-
- %p.slead
- Next just use code below to dump your public key and add to GitLab SSH Keys
-
- %pre.dark
- cat ~/.ssh/id_rsa.pub
-
- \# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc....
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "ssh", "ssh.md")).gsub("$your_email", current_user.email)
diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml
index 3e401a6e19..e24f566121 100644
--- a/app/views/help/system_hooks.html.haml
+++ b/app/views/help/system_hooks.html.haml
@@ -1,11 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title System hooks
+ %h3.page-title System hooks
- %p.slead
- Your GitLab instance can perform HTTP POST requests on the following events: create_project, delete_project, create_user, delete_user, change_team_member.
- %br
- %br
- System Hooks can be used, e.g. for logging or changing information in a LDAP server.
- %br
- %h5 Hooks request example:
- = render "admin/hooks/data_ex"
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "system_hooks", "system_hooks.md"))
diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml
index 25865251de..9e2b54ab6e 100644
--- a/app/views/help/web_hooks.html.haml
+++ b/app/views/help/web_hooks.html.haml
@@ -1,12 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Web hooks
-
- %p.slead
- Every GitLab project can trigger a web server whenever the repo is pushed to.
- %br
- Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
- %br
- GitLab will send POST request with commits information on every push.
- %h5 Hooks request example:
- = render "projects/hooks/data_ex"
+ %h3.page-title Project web hooks
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "web_hooks", "web_hooks.md"))
diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml
index 2b8950cd5c..47de4ad796 100644
--- a/app/views/help/workflow.html.haml
+++ b/app/views/help/workflow.html.haml
@@ -1,37 +1,6 @@
= render layout: 'help/layout' do
- %h3.page-title Workflow
-
- %ol.help
- %li
- %p Clone project
- .bash
- %pre.dark
- git clone git@example.com:project-name.git
-
- %li
- %p Create branch with your feature
- .bash
- %pre.dark
- git checkout -b $feature_name
-
- %li
- %p Write code. Commit changes
- .bash
- %pre.dark
- git commit -am "My feature is ready"
-
- %li
- %p Push your branch to GitLab
- .bash
- %pre.dark
- git push origin $feature_name
-
- %li
- %p Review your code on Commits page
-
- %li
- %p Create a merge request
-
- %li
- %p Your team lead will review code & merge it to main branch
+ %h3.page-title Workflow
+ .help_body
+ = preserve do
+ = markdown File.read(Rails.root.join("doc", "workflow", "workflow.md"))
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 6f9fb33226..4f7996e499 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -6,8 +6,8 @@
-# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside
= paginator.render do
- %div.pagination
- %ul
+ %div.gl-pagination
+ %ul.pagination
= prev_page_tag unless current_page.first?
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml
new file mode 100644
index 0000000000..5794e3de33
--- /dev/null
+++ b/app/views/layouts/_broadcast.html.haml
@@ -0,0 +1,4 @@
+- if broadcast_message.present?
+ .broadcast-message{ style: broadcast_styling(broadcast_message) }
+ %i.icon-bullhorn
+ = broadcast_message.message
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 0775abea3d..d9a7a2d31c 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -4,12 +4,16 @@
= "#{title} | " if defined?(title)
GitLab
= favicon_link_tag 'favicon.ico'
- = stylesheet_link_tag "application"
+ = stylesheet_link_tag "application", :media => "all"
+ = stylesheet_link_tag "print", :media => "print"
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
+ :erb
+
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
+ = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
-# Atom feed
- if current_user
@@ -20,3 +24,8 @@
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
- if current_controller?(:issues)
= auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
+
+ -# Go repository retrieval support.
+ - if controller_name == 'projects' && action_name == 'show'
+ %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
+
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 80c42fe638..fba56b5dc3 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -6,35 +6,43 @@
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
%h1 GITLAB
%span.separator
- %h1.project_name= title
- %ul.nav
- %li
- %a
- %div.hide.turbolink-spinner
- %i.icon-refresh.icon-spin
- Loading...
- %li
- = render "layouts/search"
- %li
- = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
- %i.icon-globe
- %li
- = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
- %i.icon-paste
- - if current_user.is_admin?
+ %h1.title= title
+
+ %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
+ %span.sr-only Toggle navigation
+ %i.icon-reorder
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.hidden-sm.hidden-xs
+ = render "layouts/search"
+ %li.visible-sm.visible-xs
+ = link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do
+ %i.icon-search
%li
- = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
- %i.icon-cogs
- - if current_user.can_create_project?
+ = link_to help_path, title: 'Help', class: 'has_bottom_tooltip',
+ 'data-original-title' => 'Help' do
+ %i.icon-question-sign
%li
- = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
- %i.icon-plus
- %li
- = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do
- %i.icon-user
- %li
- = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
- %i.icon-signout
- %li
- = link_to current_user, class: "profile-pic", id: 'profile-pic' do
- = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
+ = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
+ %i.icon-globe
+ %li
+ = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do
+ %i.icon-paste
+ - if current_user.is_admin?
+ %li
+ = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
+ %i.icon-cogs
+ - if current_user.can_create_project?
+ %li
+ = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
+ %i.icon-plus
+ %li
+ = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do
+ %i.icon-user
+ %li
+ = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
+ %i.icon-signout
+ %li.hidden-xs
+ = link_to current_user, class: "profile-pic", id: 'profile-pic' do
+ = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 7d16a75af6..353f7ce34f 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,4 +1,3 @@
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project)}"
- GitLab.GfmAutoComplete.Emoji.assetBase = '#{image_path("emoji")}'
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml
new file mode 100644
index 0000000000..135e8daca2
--- /dev/null
+++ b/app/views/layouts/_piwik.html.haml
@@ -0,0 +1,12 @@
+:javascript
+ var _paq = _paq || [];
+ _paq.push(["trackPageView"]);
+ _paq.push(["enableLinkTracking"]);
+
+ (function() {
+ var u=(("https:" == document.location.protocol) ? "https" : "http") + "://#{extra_config.piwik_url}/";
+ _paq.push(["setTrackerUrl", u+"piwik.php"]);
+ _paq.push(["setSiteId", "#{extra_config.piwik_site_id}"]);
+ var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0]; g.type="text/javascript";
+ g.defer=true; g.async=true; g.src=u+"piwik.js"; s.parentNode.insertBefore(g,s);
+ })();
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 3c4bd857c2..63992a22f3 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -6,17 +6,17 @@
= link_to public_root_path, class: "home" do
%h1 GITLAB
%span.separator
- %h1.project_name
- - if @project
- = project_title(@project)
- - else
- Public Projects
+ %h1.title= title
+
+ %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
+ %span.sr-only Toggle navigation
+ %i.icon-reorder
+
+ .pull-right.hidden-xs
+ = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new'
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.visible-xs
+ = link_to "Sign in", new_session_path(:user)
- %ul.nav
- %li
- %a
- %div.hide.turbolink-spinner
- %i.icon-refresh.icon-spin
- Loading...
- %li
- = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 9a0db99332..a0e55b21c3 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -7,4 +7,4 @@
= hidden_field_tag :search_code, true
= hidden_field_tag :repository_ref, @ref
= submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
- .search-autocomplete-json.hide{:'data-autocomplete-opts' => search_autocomplete_source }
+ .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 3a23cbdb37..53e0dbaef9 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -2,9 +2,10 @@
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} admin", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Admin area"
= render "layouts/flash"
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/admin'
.container
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 792fe5e4a2..511db389e0 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,9 +2,10 @@
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
%body{class: "#{app_theme} application", :'data-page' => body_data_page }
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Dashboard"
= render "layouts/flash"
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/dashboard'
.container
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index c4729836fa..5d93ffa50a 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -10,6 +10,8 @@
%p.light
GitLab is open source software to collaborate on code.
%br
- #{link_to "Sign in", new_user_session_path} or browse for #{link_to "public projects", public_projects_path}.
- %hr
+ Sign in or browse for #{link_to "public projects", public_projects_path}.
+ %hr
+ .container
+ .content
= yield
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 0e955d59ff..fb4a3a3ba9 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,10 +1,11 @@
!!! 5
%html{ lang: "en"}
- = render "layouts/head", title: "#{@group.name}"
+ = render "layouts/head", title: group_head_title
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "group: #{@group.name}"
= render "layouts/flash"
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/group'
.container
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 73946f9988..48c569f868 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -10,6 +10,8 @@
= link_to "Users", admin_users_path
= nav_link(controller: :logs) do
= link_to "Logs", admin_logs_path
+ = nav_link(controller: :broadcast_messages) do
+ = link_to "Messages", admin_broadcast_messages_path
= nav_link(controller: :hooks) do
= link_to "Hooks", admin_hooks_path
= nav_link(controller: :background_jobs) do
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index cae24c3170..12fd49e609 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -12,7 +12,7 @@
= nav_link(path: 'dashboard#merge_requests') do
= link_to merge_requests_dashboard_path do
Merge Requests
- %span.count= current_user.cared_merge_requests.opened.count
+ %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do
= link_to "Help", help_path
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index a8bb3b9155..36b102dc25 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -5,11 +5,13 @@
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
Issues
- %span.count= current_user.assigned_issues.opened.of_group(@group).count
+ - if current_user
+ %span.count= current_user.assigned_issues.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
Merge Requests
- %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
+ - if current_user
+ %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
= nav_link(path: 'groups#members') do
= link_to "Members", members_group_path(@group)
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index d44cb975ea..35d0d41750 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -4,6 +4,10 @@
%i.icon-home
= nav_link(controller: :accounts) do
= link_to "Account", profile_account_path
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path do
+ Emails
+ %span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to "Password", edit_profile_password_path
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index e9d535f697..1f70cf1798 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -4,7 +4,7 @@
%i.icon-home
- if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree)) do
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
- if project_nav_tab? :commits
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
index 7325664bc1..c43d688a2c 100644
--- a/app/views/layouts/navless.html.haml
+++ b/app/views/layouts/navless.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: @title
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: @title
= render "layouts/flash"
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index f88abeca88..991d4f0c6d 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -3,20 +3,24 @@
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab
+ :css
+ p.details {
+ font-style:italic;
+ color:#777
+ }
+ .footer p {
+ font-size:small;
+ color:#777
+ }
%body
- %h1{style: "background: #EEE; border-bottom: 1px solid #DDD; color: #474D57; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
- GitLab
- - if @project
- \|
- = link_to @project.name_with_namespace, project_url(@project), style: 'color: #29B; text-decoration: none'
- %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- = yield
- %br
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- %p{style: "font-size:small;color:#777"}
- - if @project
- You're receiving this notification because you are a member of the #{@project.name_with_namespace} project team.
+ %div.content
+ = yield
+ %div.footer{style: "margin-top: 10px;"}
+ %p
+ \—
+ %br
+ - if @project
+ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team.
+ - if @target_url
+ #{link_to "View it on GitLab", @target_url}
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 30a0532bc2..2d869a6cdc 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -2,9 +2,10 @@
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
%body{class: "#{app_theme} profile", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Profile"
= render "layouts/flash"
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/profile'
.container
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index ea739da73d..5659cfab31 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -2,19 +2,20 @@
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
= render "layouts/flash"
- if can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/project'
.container
.content
.row
- .span2
+ .col-md-2
= render "projects/settings_nav"
- .span10
+ .col-md-10
= yield
diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml
index 6d8bf9b710..3ae4961b13 100644
--- a/app/views/layouts/projects.html.haml
+++ b/app/views/layouts/projects.html.haml
@@ -1,14 +1,15 @@
!!! 5
%html{ lang: "en"}
- = render "layouts/head", title: @project.name_with_namespace
+ = render "layouts/head", title: project_head_title
%body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
= render "layouts/flash"
- if can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/project'
.container
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index f922dcc420..3c76bbb957 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,11 +1,11 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Public Projects"
- %body{class: "ui_mars application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
- if current_user
= render "layouts/head_panel", title: "Public Projects"
- else
- = render "layouts/public_head_panel"
-
+ = render "layouts/public_head_panel", title: "Public Projects"
.container.navless-container
.content= yield
diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml
new file mode 100644
index 0000000000..a289b78472
--- /dev/null
+++ b/app/views/layouts/public_group.html.haml
@@ -0,0 +1,10 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: group_head_title
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: "group: #{@group.name}"
+ %nav.main-nav.navbar-collapse.collapse
+ .container= render 'layouts/nav/group'
+ .container
+ .content= yield
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
index cfe6a63055..2a9230244f 100644
--- a/app/views/layouts/public_projects.html.haml
+++ b/app/views/layouts/public_projects.html.haml
@@ -1,9 +1,10 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "ui_mars application", :'data-page' => body_data_page}
- = render "layouts/public_head_panel"
- %nav.main-nav
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: project_title(@project)
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/project'
.container
.content= yield
diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml
new file mode 100644
index 0000000000..4aa258fea0
--- /dev/null
+++ b/app/views/layouts/public_users.html.haml
@@ -0,0 +1,8 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head", title: @title
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: @title
+ .container.navless-container
+ .content= yield
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 177b3a4f8f..97ed8ba12d 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head", title: "Search"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "Search"
= render "layouts/flash"
diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml
index e64e68d244..ce13853ed7 100644
--- a/app/views/layouts/user_team.html.haml
+++ b/app/views/layouts/user_team.html.haml
@@ -2,9 +2,10 @@
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
%body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/broadcast"
= render "layouts/head_panel", title: "team: #{@team.name}"
= render "layouts/flash"
- %nav.main-nav
+ %nav.main-nav.navbar-collapse.collapse
.container= render 'layouts/nav/team'
.container
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 88c4df55b7..5272dfa0ed 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,6 +1,2 @@
-%p
- %strong #{@note.author_name}
- left next message:
-
-%cite{style: 'color: #666'}
+%div
= markdown(@note.note)
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index 325cd44eb4..56c18cd83c 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,5 +1,2 @@
%p
= "Issue was closed by #{@updated_by.name}"
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 45770cc85d..574e8bfef2 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,9 +1,2 @@
%p
- = "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
- != merge_path_description(@merge_request, '→')
-%p
- Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
-
+ = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index ee434ec8cb..d6b76e906c 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}"
+= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml
index 5023ec737a..823ebf7734 100644
--- a/app/views/notify/group_access_granted_email.html.haml
+++ b/app/views/notify/group_access_granted_email.html.haml
@@ -1,5 +1,4 @@
%p
= "You have been granted #{@membership.human_access} access to group"
-%p
= link_to group_url(@group) do
= @group.name
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
index 7706b3f751..482c884a9d 100644
--- a/app/views/notify/issue_status_changed_email.html.haml
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -1,5 +1,2 @@
%p
= "Issue was #{@issue_status} by #{@updated_by.name}"
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index e2bc9cf5c0..6762fae7f6 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,9 +1,2 @@
%p
- = "Merge Request #{@merge_request.iid} was merged"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
- != merge_path_description(@merge_request, '→')
-%p
- Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
-
+ = "Merge Request ##{@merge_request.iid} was merged"
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 550f677fed..360da60bc3 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.iid} was merged"
+= "Merge Request ##{@merge_request.iid} was merged"
Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/new_email_email.html.haml b/app/views/notify/new_email_email.html.haml
new file mode 100644
index 0000000000..4a0448a573
--- /dev/null
+++ b/app/views/notify/new_email_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Hi #{@user.name}!
+%p
+ A new email was added to your account:
+%p
+ email:
+ %code= @email.email
+%p
+ If this email was added in error, you can remove it here:
+ = link_to "Emails", profile_emails_url
diff --git a/app/views/notify/new_email_email.text.erb b/app/views/notify/new_email_email.text.erb
new file mode 100644
index 0000000000..51cba99ad0
--- /dev/null
+++ b/app/views/notify/new_email_email.text.erb
@@ -0,0 +1,7 @@
+Hi <%= @user.name %>!
+
+A new email was added to your account:
+
+email.................. <%= @email.email %>
+
+If this email was added in error, you can remove it here: <%= profile_emails_url %>
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 3b3c148517..f2f8eee18c 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,9 +1,6 @@
-%p
- New Issue was created.
-%p
- = "Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
-%p
- Author: #{@issue.author_name}
-%p
- Assignee: #{@issue.assignee_name}
+-if @issue.description
+ = markdown(@issue.description)
+
+- if @issue.assignee_id.present?
+ %p
+ Assignee: #{@issue.assignee_name}
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 6b2782ebb0..f02d5111b2 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -1,9 +1,9 @@
-%p
- = "New Merge Request !#{@merge_request.iid}"
-%p
- = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
-%p
+%p.details
!= merge_path_description(@merge_request, '→')
-%p
- Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
+- if @merge_request.assignee_id.present?
+ %p
+ Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
+
+-if @merge_request.description
+ = markdown(@merge_request.description)
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 2d27350486..16be4bb619 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-New Merge Request <%= @merge_request.iid %>
+New Merge Request #<%= @merge_request.iid %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml
index 620b258fc1..1d961e4424 100644
--- a/app/views/notify/note_commit_email.html.haml
+++ b/app/views/notify/note_commit_email.html.haml
@@ -1,5 +1,2 @@
-%p
- = "New comment for Commit #{@commit.short_id}"
- = link_to_gfm truncate(@commit.title, length: 16), project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")
= render 'note_message'
diff --git a/app/views/notify/note_issue_email.html.haml b/app/views/notify/note_issue_email.html.haml
index b3230953e7..2fa2f78466 100644
--- a/app/views/notify/note_issue_email.html.haml
+++ b/app/views/notify/note_issue_email.html.haml
@@ -1,4 +1 @@
-%p
- = "New comment for Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 35), project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")
= render 'note_message'
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index d587b06848..65f0e4c406 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,8 +1,7 @@
-%p
- - if @note.for_diff_line?
- = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
- - else
- = link_to "New comment", project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
- for Merge Request ##{@merge_request.iid}
- %cite "#{truncate(@merge_request.title, length: 20)}"
+- if @note.diff_file_name
+ %p.details
+ New comment on diff for
+ = link_to @note.diff_file_name, @target_url
+ \:
+
= render 'note_message'
diff --git a/app/views/notify/note_wall_email.html.haml b/app/views/notify/note_wall_email.html.haml
index 92200e83ef..2fa2f78466 100644
--- a/app/views/notify/note_wall_email.html.haml
+++ b/app/views/notify/note_wall_email.html.haml
@@ -1,5 +1 @@
-%p
- New message on
- = link_to "Project Wall", project_wall_url(@note.project, anchor: "note_#{@note.id}")
-
= render 'note_message'
diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml
index b0edff44ce..07227a3e68 100644
--- a/app/views/notify/reassigned_issue_email.html.haml
+++ b/app/views/notify/reassigned_issue_email.html.haml
@@ -1,6 +1,3 @@
-%p
- = "Reassigned Issue ##{@issue.iid}"
- = link_to_gfm truncate(@issue.title, length: 30), project_issue_url(@issue.project, @issue)
%p
Assignee changed
- if @previous_assignee
diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml
index 3a615f86e6..00aee6bc95 100644
--- a/app/views/notify/reassigned_merge_request_email.html.haml
+++ b/app/views/notify/reassigned_merge_request_email.html.haml
@@ -1,6 +1,3 @@
-%p
- = "Reassigned Merge Request !#{@merge_request.iid}"
- = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request)
%p
Assignee changed
- if @previous_assignee
diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb
index eecf055ff6..87a7847e06 100644
--- a/app/views/notify/reassigned_merge_request_email.text.erb
+++ b/app/views/notify/reassigned_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-Reassigned Merge Request <%= @merge_request.iid %>
+Reassigned Merge Request #<%= @merge_request.iid %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
new file mode 100644
index 0000000000..85a01a567f
--- /dev/null
+++ b/app/views/notify/repository_push_email.html.haml
@@ -0,0 +1,28 @@
+%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
+
+%h4 Commits:
+
+%ul
+ - @commits.each do |commit|
+ %li
+ #{commit.short_id} - #{commit.title}
+
+%h4 Changes:
+- @diffs.each do |diff|
+ %li
+ %strong
+ - if diff.old_path == diff.new_path
+ = diff.new_path
+ - elsif diff.new_path && diff.old_path
+ #{diff.old_path} → #{diff.new_path}
+ - else
+ = diff.new_path || diff.old_path
+ %hr
+ %pre
+ = diff.diff
+ %br
+
+- if @compare.timeout
+ %h5 To prevent performance issues changes are hidden
+- elsif @compare.commits_over_limit?
+ %h5 Changes are not shown due to large amount of commits
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
new file mode 100644
index 0000000000..a15b8efe1f
--- /dev/null
+++ b/app/views/notify/repository_push_email.text.haml
@@ -0,0 +1,25 @@
+#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
+
+\
+Commits:
+- @commits.each do |commit|
+ #{commit.short_id} - #{truncate(commit.title, length: 40)}
+\
+\
+Changes:
+- @diffs.each do |diff|
+ \
+ \=====================================
+ - if diff.old_path == diff.new_path
+ = diff.new_path
+ - elsif diff.new_path && diff.old_path
+ #{diff.old_path} → #{diff.new_path}
+ - else
+ = diff.new_path || diff.old_path
+ \=====================================
+ != diff.diff
+\
+- if @compare.timeout
+ Huge diff. To prevent performance issues it was hidden
+- elsif @compare.commits_over_limit?
+ Changes are not shown due to large amount of commits
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index b76339027a..c60e1ca929 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -23,8 +23,9 @@
%p.cgray
- if current_user.private_token
- = text_field_tag "token", current_user.private_token, class: "input-xlarge input-xpadding pull-left"
- = f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token prepend-left-10"
+ = text_field_tag "token", current_user.private_token, class: "form-control"
+ %div
+ = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
= f.submit 'Generate', class: "btn success btn-build-token"
@@ -47,9 +48,12 @@
%p
Changing your username will change path to all personal projects!
%div
- = f.text_field :username, required: true, class: 'input-xlarge input-xpadding'
+ = f.text_field :username, required: true, class: 'form-control'
- %span.loading-gif.hide= image_tag "ajax_loader.gif"
+ .loading-gif.hide
+ %p
+ %i.icon-spinner.icon-spin
+ Saving new username
%p.light
= user_url(@user)
%div
@@ -70,4 +74,4 @@
%li
The following groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
- = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove"
+ = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
new file mode 100644
index 0000000000..b5f1e438cc
--- /dev/null
+++ b/app/views/profiles/emails/index.html.haml
@@ -0,0 +1,33 @@
+%h3.page-title
+ My email addresses
+%p.light
+ Your
+ %b Primary Email
+ will be used for account notifications, avatar detection and web based operations, such as edits and merges.
+ %br
+ All email addresses will be used to identify your commits.
+
+%hr
+
+.ui-box
+ .title
+ Emails (#{@emails.count + 1})
+ %ul.well-list#emails-table
+ %li
+ %strong= @primary
+ %span.label.label-success Primary Email
+ - @emails.each do |email|
+ %li
+ %strong= email.email
+ %span.cgray
+ added #{time_ago_with_tooltip(email.created_at)}
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right'
+
+%h4 Add email address
+= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
+ .form-group
+ = f.label :email, class: 'control-label'
+ .col-sm-10
+ = f.text_field :email, class: 'form-control'
+ .form-actions
+ = f.submit 'Add', class: 'btn btn-create'
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
index 6fc27be5db..cebc0b6bcc 100644
--- a/app/views/profiles/groups/index.html.haml
+++ b/app/views/profiles/groups/index.html.haml
@@ -18,13 +18,14 @@
%li
.pull-right
- if can?(current_user, :manage_group, group)
- = link_to edit_group_path(group), class: "btn-small btn grouped" do
+ = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do
%i.icon-cogs
Settings
- = link_to leave_profile_group_path(group), confirm: "Are you sure you want to leave #{group.name} group?", method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do
- %i.icon-signout
- Leave
+ - if can?(current_user, :destroy, user_group)
+ = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do
+ %i.icon-signout
+ Leave
= link_to group, class: 'group-name' do
%strong= group.name
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 158979c0ee..f905417f0e 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,20 +1,18 @@
%div
- = form_for [:profile, @key] do |f|
+ = form_for [:profile, @key], html: { class: 'form-horizontal' } do |f|
- if @key.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
- .control-group
- = f.label :title
- .controls= f.text_field :title, class: "input-xlarge"
- .control-group
- = f.label :key
- .controls
- %p.light
- Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_ssh_path}.
- = f.text_area :key, class: "input-xxlarge thin_area"
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10= f.text_field :title, class: "form-control"
+ .form-group
+ = f.label :key, class: 'control-label'
+ .col-sm-10
+ = f.text_area :key, class: "form-control", rows: 8
.form-actions
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index d0a3fe32c3..81411a7565 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -4,8 +4,6 @@
%span
(#{key.fingerprint})
%span.cgray
- added
- = time_ago_in_words(key.created_at)
- ago
+ added #{time_ago_with_tooltip(key.created_at)}
- = link_to 'Remove', profile_key_path(key), confirm: 'Are you sure?', method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
+ = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 79fdb16408..71a4ca91d4 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -17,6 +17,6 @@
= render @keys
- if @keys.blank?
%li
- %h3.nothing_here_message There are no SSH keys with access to your account.
+ .nothing-here-block There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index f1b8fe08d5..3d5a947948 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -1,4 +1,6 @@
%h3.page-title Add an SSH Key
+%p.light
+ Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_ssh_path}.
%hr
= render 'form'
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 2852c338af..b6724a7cb5 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,5 +1,5 @@
.row
- .span4
+ .col-md-4
.ui-box
.title
SSH Key
@@ -11,7 +11,7 @@
%span.light Created on:
%strong= @key.created_at.stamp("Aug 21, 2011")
- .span8
+ .col-md-8
%p
%span.light Fingerprint:
%strong= @key.fingerprint
@@ -19,4 +19,4 @@
= @key.key
.pull-right
- = link_to 'Remove', profile_key_path(@key), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove delete-key"
+ = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
index 5f62c8099d..218d51d31a 100644
--- a/app/views/profiles/notifications/_settings.html.haml
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -1,31 +1,17 @@
%li
- .row
- .span4
- %span
- = notification_icon(notification)
-
- - if membership.kind_of? UsersGroup
- = link_to membership.group.name, membership.group
- - else
- = link_to_project(membership.project)
- .span7
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
- = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type')
- = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id')
-
- = label_tag do
- = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Use global setting
-
- = label_tag do
- = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Disabled
-
- = label_tag do
- = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Participating
-
- = label_tag do
- = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(membership, 'notification_level'), class: 'trigger-submit'
- %span Watch
+ %span.notification-icon-holder
+ - if notification.global?
+ = notification_icon(@notification)
+ - else
+ = notification_icon(notification)
+ %span.str-truncated
+ - if membership.kind_of? UsersGroup
+ = link_to membership.group.name, membership.group
+ - else
+ = link_to_project(membership.project)
+ .pull-right
+ = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
+ = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type')
+ = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id')
+ = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'trigger-submit'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 8353b2f5f2..efe9c03219 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -3,56 +3,49 @@
%p.light
GitLab uses the email specified in your profile for notifications
%hr
-.alert.alert-info
- %p
- %i.icon-circle.cred
- %strong Disabled
- – You will not get any notifications via email
- %p
- %i.icon-circle.cblue
- %strong Participating
- – You will only receive notifications from related resources (e.g. from your commits or assigned issues)
- %p
- %i.icon-circle.cgreen
- %strong Watch
- – You will receive all notifications from projects in which you participate
+= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do
+ = hidden_field_tag :notification_type, 'global'
-.row
- .span4
- %h4
- = notification_icon(@notification)
- Global setting
- .span7
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
- = hidden_field_tag :notification_type, 'global'
-
- = label_tag do
+ = label_tag :notification_level, 'Notification level', class: 'control-label'
+ .col-sm-10
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
- %span Disabled
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- = label_tag do
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
- %span Participating
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
- = label_tag do
+ .radio
+ = label_tag nil, class: '' do
= radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
- %span Watch
+ .level-title
+ Watch
+ %p You will receive all notifications from projects in which you participate
-%br
-= link_to '#', class: 'js-toggle-visibility-link' do
- %span.btn.btn-tiny
- %i.icon-chevron-down
- %span Advanced notifications settings
-.js-toggle-visibility-container.hide
+.clearfix
%hr
- %h4 Groups:
- %ul.bordered-list
- - @users_groups.each do |users_group|
- - notification = Notification.new(users_group)
- = render 'settings', type: 'group', membership: users_group, notification: notification
+ %p
+ You can also specify notification level per group or per project
+ %br
+ By default all projects and groups uses notification level set above
+.row.all-notifications
+ .col-md-6
+ %h4 Groups:
+ %ul.bordered-list
+ - @users_groups.each do |users_group|
+ - notification = Notification.new(users_group)
+ = render 'settings', type: 'group', membership: users_group, notification: notification
- %h4 Projects:
- %ul.bordered-list
- - @users_projects.each do |users_project|
- - notification = Notification.new(users_project)
- = render 'settings', type: 'project', membership: users_project, notification: notification
+ .col-md-6
+ %h4 Projects:
+ %ul.bordered-list
+ - @users_projects.each do |users_project|
+ - notification = Notification.new(users_project)
+ = render 'settings', type: 'project', membership: users_project, notification: notification
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index a5fa6e7f18..2a7d317aa3 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -3,30 +3,31 @@
Change your password or recover your current one.
%hr
.update-password
- = form_for @user, url: profile_password_path, method: :put do |f|
+ = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
You must provide current password in order to change it.
%br
After a successful password update you will be redirected to login page where you should login with your new password
-if @user.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
- .control-group
- = f.label :current_password
- .controls
- = f.password_field :current_password, required: true
+ .form-group
+ = f.label :current_password, class: 'control-label'
+ .col-sm-10
+ = f.password_field :current_password, required: true, class: 'form-control'
%div
= link_to "Forgot your password?", reset_profile_password_path, method: :put
- .control-group
- = f.label :password, 'New password'
- .controls= f.password_field :password, required: true
- .control-group
- = f.label :password_confirmation
- .controls
- = f.password_field :password_confirmation, required: true
+ .form-group
+ = f.label :password, 'New password', class: 'control-label'
+ .col-sm-10
+ = f.password_field :password, required: true, class: 'form-control'
+ .form-group
+ = f.label :password_confirmation, class: 'control-label'
+ .col-sm-10
+ = f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
= f.submit 'Save password', class: "btn btn-save"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index a4e7dadd16..b72232ee36 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -1,22 +1,22 @@
-= form_for @user, url: profile_password_path, method: :post do |f|
- .light-well.padded
- %p.slead
- Please set new password before proceed.
- %br
- After successful password update you will be redirected to login screen
- -if @user.errors.any?
- .alert.alert-error
- %ul
- - @user.errors.full_messages.each do |msg|
- %li= msg
+%h3.page-title Setup new password
+%hr
+= form_for @user, url: profile_password_path, method: :post, html: { class: 'form-horizontal '} do |f|
+ %p.slead
+ Please set a new password before proceeding.
+ %br
+ After a successful password update you will be redirected to login screen.
+ -if @user.errors.any?
+ .alert.alert-danger
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
- .control-group
- = f.label :password
- .controls= f.password_field :password, required: true
- .control-group
- = f.label :password_confirmation
- .controls
- = f.password_field :password_confirmation, required: true
- .control-group
- .controls
- = f.submit 'Set new password', class: "btn btn-create"
+ .form-group
+ = f.label :password, class: 'control-label'
+ .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+ .form-group
+ = f.label :password_confirmation, class: 'control-label'
+ .col-sm-10
+ = f.password_field :password_confirmation, required: true, class: 'form-control'
+ .form-actions
+ = f.submit 'Set new password', class: "btn btn-create"
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index d382d2d70c..69598e799e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -8,62 +8,83 @@
-= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" } do |f|
+= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f|
-if @user.errors.any?
- %div.alert.alert-error
+ %div.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
.row
- .span7
- .control-group
+ .col-md-7
+ .form-group
= f.label :name, class: "control-label"
- .controls
- = f.text_field :name, class: "input-xlarge", required: true
+ .col-sm-10
+ = f.text_field :name, class: "form-control", required: true
%span.help-block Enter your name, so people you know can recognize you.
- .control-group
+
+ .form-group
= f.label :email, class: "control-label"
- .controls
- = f.text_field :email, class: "input-xlarge", required: true
- - if @user.unconfirmed_email.present?
- %span.help-block
- We sent confirmation email to
- %strong #{@user.unconfirmed_email}
+ .col-sm-10
+ - if @user.ldap_user?
+ = f.text_field :email, class: "form-control", required: true, readonly: true
+ %span.help-block.light
+ Email is read-only for LDAP user
- else
- %span.help-block We also use email for avatar detection if no avatar is uploaded.
- .control-group
+ - if @user.temp_oauth_email?
+ = f.text_field :email, class: "form-control", required: true, value: nil
+ - else
+ = f.text_field :email, class: "form-control", required: true
+ - if @user.unconfirmed_email.present?
+ %span.help-block
+ Please click the link in the confirmation email before continuing, it was send to
+ %strong #{@user.unconfirmed_email}
+
+ - else
+ %span.help-block We also use email for avatar detection if no avatar is uploaded.
+ .form-group
= f.label :skype, class: "control-label"
- .controls= f.text_field :skype, class: "input-xlarge"
- .control-group
+ .col-sm-10= f.text_field :skype, class: "form-control"
+ .form-group
= f.label :linkedin, class: "control-label"
- .controls= f.text_field :linkedin, class: "input-xlarge"
- .control-group
+ .col-sm-10= f.text_field :linkedin, class: "form-control"
+ .form-group
= f.label :twitter, class: "control-label"
- .controls= f.text_field :twitter, class: "input-xlarge"
- .control-group
+ .col-sm-10= f.text_field :twitter, class: "form-control"
+ .form-group
+ = f.label :website_url, 'Website', class: "control-label"
+ .col-sm-10= f.text_field :website_url, class: "form-control"
+ .form-group
= f.label :bio, class: "control-label"
- .controls
- = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250
+ .col-sm-10
+ = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
- .span5.pull-right
+ .col-md-5
.light-well
= image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
.clearfix
.profile-avatar-form-option
%p.light
- You can upload an avatar here
- %br
- or change it at #{link_to "gravatar.com", "http://gravatar.com"}
+ - if @user.avatar?
+ You can change your avatar here
+ %br
+ or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"}
+ - else
+ You can upload an avatar here
+ %br
+ or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
%a.choose-btn.btn.btn-small.js-choose-user-avatar-button
%i.icon-paper-clip
%span Choose File ...
%span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-user-avatar-input hide"
- %span.help-block The maximum file size allowed is 100KB.
+ = f.file_field :avatar, class: "js-user-avatar-input hidden"
+ .light The maximum file size allowed is 100KB.
+ - if @user.avatar?
+ %hr
+ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
index e646d04282..e283bd2bf1 100644
--- a/app/views/projects/_dropdown.html.haml
+++ b/app/views/projects/_dropdown.html.haml
@@ -6,15 +6,19 @@
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
%li
= link_to url_for_new_issue, title: "New Issue" do
- Issue
+ New issue
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
%li
= link_to new_project_merge_request_path(@project), title: "New Merge Request" do
- Merge Request
+ New merge request
- if @project.snippets_enabled && can?(current_user, :write_snippet, @project)
%li
= link_to new_project_snippet_path(@project), title: "New Snippet" do
- Snippet
+ New snippet
+ - if can?(current_user, :admin_team_member, @project)
+ %li
+ = link_to new_project_team_member_path(@project), title: "New project member" do
+ New project member
- if can? current_user, :push_code, @project
%li.divider
%li
@@ -26,9 +30,4 @@
%i.icon-tag
Git tag
- - if can?(current_user, :admin_team_member, @project)
- %li.divider
- %li
- = link_to new_project_team_member_path(@project), title: "New project member" do
- Project member
diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml
index bb9759353a..7c8bb33ed7 100644
--- a/app/views/projects/_errors.html.haml
+++ b/app/views/projects/_errors.html.haml
@@ -1,4 +1,4 @@
- if @project.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} ×
= @project.errors.full_messages.first
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
new file mode 100644
index 0000000000..82ec68c916
--- /dev/null
+++ b/app/views/projects/_home_panel.html.haml
@@ -0,0 +1,31 @@
+- empty_repo = @project.empty_repo?
+.project-home-panel{:class => ("empty-project" if empty_repo)}
+ .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(@project.visibility_level)} project" }
+ = visibility_level_icon(@project.visibility_level)
+ .row
+ .col-sm-6
+ %h4.project-home-title
+ = @project.name_with_namespace
+
+ .col-sm-6
+ - unless empty_repo
+ .project-home-dropdown
+ = render "dropdown"
+ = render "shared/clone_panel"
+
+ .project-home-extra.row
+ .col-md-8
+ .project-home-desc
+ - if @project.description.present?
+ = @project.description
+ - if can?(current_user, :admin_project, @project)
+ –
+ %strong= link_to 'Edit', edit_project_path
+
+ - unless empty_repo
+ .col-md-4
+ .project-home-links
+ = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
+ = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project)
+ = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project)
+ %span.light.prepend-left-20= repository_size
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index f59e2871aa..4c7b088ae9 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -1,21 +1,21 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), class: "stat-tab tab " do
%i.icon-edit
- Edit Project
+ Project
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
%i.icon-group
Members
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
- %span
+ %i.icon-key
Deploy Keys
= nav_link(controller: :hooks) do
= link_to project_hooks_path(@project) do
- %span
+ %i.icon-link
Web Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
- %span
+ %i.icon-cogs
Services
diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml
new file mode 100644
index 0000000000..eb38fce0ec
--- /dev/null
+++ b/app/views/projects/_visibility_level.html.haml
@@ -0,0 +1,27 @@
+.form-group.project-visibility-level-holder
+ = f.label :visibility_level, class: 'control-label' do
+ Visibility Level
+ = link_to "(?)", help_public_access_path
+ .col-sm-10
+ - if can_change_visibility_level
+ - Gitlab::VisibilityLevel.values.each do |level|
+ .radio
+ - restricted = restricted_visibility_levels.include?(level)
+ = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
+ = label :project_visibility_level, level do
+ = visibility_level_icon(level)
+ .option-title
+ = visibility_level_label(level)
+ .option-descr
+ = visibility_level_description(level)
+ - unless restricted_visibility_levels.empty?
+ .col-sm-10
+ %span.info
+ Some visibility level settings have been restricted by the administrator.
+ - else
+ .col-sm-10
+ %span.info
+ = visibility_level_icon(visibility_level)
+ %strong
+ = visibility_level_label(visibility_level)
+ .light= visibility_level_description(visibility_level)
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index f6cc62e707..2f82bfe52f 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -4,7 +4,7 @@
- if allowed_tree_edit?
= link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small"
- else
- %span.btn.btn-small.disabled Edit
+ %span.btn.btn-small.disabled edit
= link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank"
-# only show normal/blame view links for text files
- if @blob.text?
@@ -13,3 +13,7 @@
- else
= link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty?
= link_to "history", project_commits_path(@project, @id), class: "btn btn-small"
+
+ - if allowed_tree_edit?
+ = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
+ remove
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 6524aaeef4..863e4e3de5 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -4,7 +4,6 @@
= link_to project_tree_path(@project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
- \/
%li
- if path
- if path.end_with?(@path)
@@ -16,14 +15,18 @@
- else
= link_to title, '#'
+%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
+ - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+ = render blob_commit, project: @project
+
%div#tree-content-holder.tree-content-holder
.file-holder
- .file-title
+ .file-title.clearfix
%i.icon-file
%span.file_name
= blob.name
%small= number_to_human_size blob.size
- %span.options= render "actions"
+ %span.options.hidden-xs= render "actions"
- if blob.text?
= render "text", blob: blob
- elsif blob.image?
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
new file mode 100644
index 0000000000..692248dd23
--- /dev/null
+++ b/app/views/projects/blob/_remove.html.haml
@@ -0,0 +1,23 @@
+#modal-remove-blob.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title Remove #{@blob.name}
+ %p.light
+ From branch
+ %strong= @ref
+
+ .modal-body
+ = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do
+ .form-group.commit_message-group
+ = label_tag 'commit_message', class: "control-label" do
+ Commit message
+ .col-sm-10
+ = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
+ params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control')}
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = submit_tag 'Remove file', class: 'btn btn-remove'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index bed493d6d8..7cbea7c3eb 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -4,11 +4,10 @@
= markdown(blob.data)
- elsif markup?(blob.name)
.file-content.wiki
- = raw GitHub::Markup.render(blob.name, blob.data)
+ = render_markup(blob.name, blob.data)
- else
.file-content.code
- unless blob.empty?
- %div{class: user_color_scheme_class}
- = raw blob.colorize(formatter: :gitlab)
+ = render 'shared/file_hljs', blob: blob
- else
- %p.nothing_here_message Empty file
+ .nothing-here-block Empty file
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index d96595bc7f..69167654c3 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,4 +1,8 @@
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
+
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
+
+- if allowed_tree_edit?
+ = render 'projects/blob/remove'
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 4372647a41..f073197709 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,4 +1,4 @@
-- commit = Commit.new(Gitlab::Git::Commit.new(branch.commit))
+- commit = @repository.commit(branch.target)
%li
%h4
= link_to project_commits_path(@project, branch.name) do
@@ -10,23 +10,24 @@
%i.icon-lock
.pull-right
- if can?(current_user, :download_code, @project)
- = link_to archive_project_repository_path(@project, ref: branch.name), class: 'btn grouped btn-small' do
- %i.icon-download-alt
- Download
- = link_to project_compare_index_path(@project, from: branch.name, to: branch.name), class: 'btn grouped btn-small', title: "Compare" do
- %i.icon-copy
- Compare
+ = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small'
+ - if branch.name != @repository.root_ref
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do
+ %i.icon-copy
+ Compare
- if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref
- = link_to project_branch_path(@project, branch.name), class: 'btn grouped btn-small remove-row', method: :delete, confirm: 'Removed branch cannot be restored. Are you sure?', remote: true do
+ = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
%i.icon-trash
- %p
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: ''
- %span.light
- = gfm escape_once(truncate(commit.title, length: 40))
- %span
- = time_ago_in_words(commit.committed_date)
- ago
+ - if commit
+ %p
+ = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: ''
+ %span.light
+ = gfm escape_once(truncate(commit.title, length: 40))
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ %p
+ Cant find HEAD commit for this branch
diff --git a/app/views/projects/branches/_filter.html.haml b/app/views/projects/branches/_filter.html.haml
index 7ea11a74a2..7e1478292e 100644
--- a/app/views/projects/branches/_filter.html.haml
+++ b/app/views/projects/branches/_filter.html.haml
@@ -1,12 +1,22 @@
%ul.nav.nav-pills.nav-stacked
= nav_link(path: 'branches#recent') do
- = link_to 'Recent', recent_project_branches_path(@project)
+ = link_to recent_project_branches_path(@project) do
+ Recent
+ .pull-right
+ = @repository.recent_branches.count
+
= nav_link(path: 'protected_branches#index') do
= link_to project_protected_branches_path(@project) do
Protected
%i.icon-lock
+ .pull-right
+ = @project.protected_branches.count
+
= nav_link(path: 'branches#index') do
- = link_to 'All branches', project_branches_path(@project)
+ = link_to project_branches_path(@project) do
+ All branches
+ .pull-right
+ = @repository.branch_names.count
%hr
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 45b9c6c852..bee04eb013 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,10 +1,10 @@
= render "projects/commits/head"
.row
- .span3
+ .col-md-3
= render "filter"
- .span9
+ .col-md-9
- unless @branches.empty?
- %ul.bordered-list.top-list
+ %ul.bordered-list.top-list.all-branches
- @branches.each do |branch|
= render "projects/branches/branch", branch: branch
- = paginate @branches, theme: 'gitlab'
\ No newline at end of file
+ = paginate @branches, theme: 'gitlab'
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 37612d3fd7..5da2ede293 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -1,16 +1,15 @@
%h3.page-title
%i.icon-code-fork
New branch
-= form_tag project_branches_path, method: :post do
- .control-group
+= form_tag project_branches_path, method: :post, class: "form-horizontal" do
+ .form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
- .controls
- = text_field_tag :branch_name, nil, placeholder: 'feature/dashboard', required: true, tabindex: 1
- .control-group
+ .col-sm-10
+ = text_field_tag :branch_name, nil, placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
+ .form-group
= label_tag :ref, 'Create from', class: 'control-label'
- .controls
- = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2
- .light branch name or commit SHA
+ .col-sm-10
+ = text_field_tag :ref, nil, placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions
= submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/branches/recent.html.haml b/app/views/projects/branches/recent.html.haml
index 25f416c78f..37d7919121 100644
--- a/app/views/projects/branches/recent.html.haml
+++ b/app/views/projects/branches/recent.html.haml
@@ -1,8 +1,8 @@
= render "projects/commits/head"
.row
- .span3
+ .col-md-3
= render "filter"
- .span9
+ .col-md-9
%ul.bordered-list.top-list
- @branches.each do |branch|
- = render "projects/branches/branch", branch: branch
\ No newline at end of file
+ = render "projects/branches/branch", branch: branch
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 111446db58..f2b0699f13 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,18 +1,18 @@
.pull-right
%div
- if @notes_count > 0
- %span.btn.disabled.grouped
+ %span.btn.disabled.btn-grouped
%i.icon-comment
= @notes_count
.pull-left.btn-group
- %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
+ %a.btn.btn-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: "btn btn-primary grouped" do
+ = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do
%span Browse Code »
%div
@@ -23,16 +23,14 @@
%span.light Authored by
%strong
= commit_author_link(@commit, avatar: true, size: 24)
- %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")}
- #{time_ago_in_words(@commit.authored_date)} ago
+ #{time_ago_with_tooltip(@commit.authored_date)}
- if @commit.different_committer?
.commit-info-row
%span.light Committed by
%strong
= commit_committer_link(@commit, avatar: true, size: 24)
- %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")}
- #{time_ago_in_words(@commit.committed_date)} ago
+ #{time_ago_with_tooltip(@commit.committed_date)}
.commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent")
@@ -49,7 +47,7 @@
- if @branches.any?
and in
= link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand")
- %span.js-details-contain.hide
+ %span.js-details-content.hide
= commit_branches_links(@project, @branches)
.commit-box
diff --git a/app/views/projects/commit/huge_commit.html.haml b/app/views/projects/commit/huge_commit.html.haml
index 210d8d9e20..398ce77142 100644
--- a/app/views/projects/commit/huge_commit.html.haml
+++ b/app/views/projects/commit/huge_commit.html.haml
@@ -1,3 +1,3 @@
= render "projects/commit/commit_box"
-.alert.alert-error
+.alert.alert-danger
%h4 Commit diffs are too big to be displayed
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index d70fd96acc..a060666280 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,19 +1,25 @@
-%li.commit
+%li.commit.js-toggle-container
.commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
- = link_to_gfm truncate(commit.title, length: 70), project_commit_path(project, commit.id), class: "commit-row-message"
+ %span.str-truncated
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ - if commit.description?
+ %a.text-expander.js-toggle-button ...
+
= link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
.notes_count
- notes = project.notes.for_commit_id(commit.id)
- if notes.any?
- %span.badge.badge-info
+ %span.label.label-gray
%i.icon-comment
= notes.count
+ - if commit.description?
+ .commit-row-description.js-toggle-content
+ = simple_format(commit.description)
+
.commit-row-info
= commit_author_link(commit, avatar: true, size: 16)
- %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
- = time_ago_in_words(commit.committed_date)
- ago
-
+ .committed_ago
+ #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index b640477807..e3411b62eb 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,11 +1,11 @@
- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
.row.commits-row
- .span2
+ .col-md-2
%h4
%i.icon-calendar
%span= day.stamp("28 Aug, 2010")
%p= pluralize(commits.count, 'commit')
- .span10
+ .col-md-10
%ul.well-list
= render commits, project: @project
%hr.lists-separator
diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml
index 2e61b9ece1..466085139f 100644
--- a/app/views/projects/commits/_diffs.html.haml
+++ b/app/views/projects/commits/_diffs.html.haml
@@ -1,6 +1,6 @@
- @suppress_diff ||= @suppress_diff || @force_suppress_diff
- if @suppress_diff
- .alert.alert-block
+ .alert.alert-warning
%p
%strong Warning! This is a large diff.
%p
@@ -8,18 +8,18 @@
- if current_controller?(:commit) or current_controller?(:merge_requests)
Please, download the diff as
- if current_controller?(:commit)
- = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined_link"
+ = link_to "plain diff", project_commit_path(@project, @commit, format: :diff), class: "underlined-link"
or
- = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined_link"
+ = link_to "email patch", project_commit_path(@project, @commit, format: :patch), class: "underlined-link"
- else
- = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined_link"
+ = link_to "plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "underlined-link"
or
- = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined_link"
+ = link_to "email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "underlined-link"
instead.
- unless @force_suppress_diff
%p
If you still want to see the diff
- = link_to "click this link", url_for(force_show_diff: true), class: "underlined_link"
+ = link_to "click this link", url_for(force_show_diff: true), class: "underlined-link"
%p.commit-stat-summary
Showing
@@ -30,6 +30,10 @@
%strong.cgreen #{@commit.stats.additions} additions
and
%strong.cred #{@commit.stats.deletions} deletions
+ - if params[:view] == 'parallel'
+ = link_to "Inline Diff", url_for(view: 'inline'), {id: "commit-diff-viewtype", class: 'btn btn-tiny pull-right'}
+ - else
+ = link_to "Side-by-side Diff", url_for(view: 'parallel'), {id: "commit-diff-viewtype", class: 'btn btn-tiny pull-right'}
.file-stats
= render "projects/commits/diff_head", diffs: diffs
@@ -40,31 +44,47 @@
- file = project.repository.blob_at(@commit.id, diff.new_path)
- file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file
- next unless file
- .file{id: "diff-#{i}"}
- .header
+ .diff-file.js-toggle-container{id: "diff-#{i}"}
+ .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"}
- if diff.deleted_file
%span= diff.old_path
- - if @commit.parent_ids.present?
- = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
- View file @
- %span.commit-short-id= @commit.short_id(6)
+ .diff-btn-group
+ - if @commit.parent_ids.present?
+ = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do
+ View file @
+ %span.commit-short-id= @commit.short_id(6)
- else
%span= diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
- View file @
- %span.commit-short-id= @commit.short_id(6)
+ .diff-btn-group
+ = link_to "#", class: "js-toggle-button btn btn-small" do
+ %i.icon-chevron-down
+ Diff comments
+
- .content
+ - if @merge_request && @merge_request.source_project
+ = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
+ Edit
+
+
+ = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do
+ View file @
+ %span.commit-short-id= @commit.short_id(6)
+
+
+ .diff-content
-# Skipp all non non-supported blobs
- next unless file.respond_to?('text?')
- if file.text?
- = render "projects/commits/text_file", diff: diff, index: i
+ - if params[:view] == 'parallel'
+ = render "projects/commits/parallel_view", diff: diff, project: project, file: file, index: i
+ - else
+ = render "projects/commits/text_file", diff: diff, index: i
- elsif file.image?
- old_file = project.repository.blob_at(@commit.parent_id, diff.old_path) if @commit.parent_id
= render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
- else
- %p.nothing_here_message No preview for this file type
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index c2da9f273b..81e3374391 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -22,6 +22,6 @@
- if current_user && current_controller?(:commits) && current_user.private_token
- %li.pull-right
+ %li.pull-right.hidden-sm
= link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed" do
%i.icon-rss
diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/commits/_image.html.haml
index 73f87289d3..6d9ef5964d 100644
--- a/app/views/projects/commits/_image.html.haml
+++ b/app/views/projects/commits/_image.html.haml
@@ -15,7 +15,7 @@
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
%b W:
- %span.meta-width
+ %span.meta-width
|
%b H:
%span.meta-height
@@ -27,7 +27,7 @@
%span.meta-filesize= "#{number_to_human_size file.size}"
|
%b W:
- %span.meta-width
+ %span.meta-width
|
%b H:
%span.meta-height
@@ -60,4 +60,4 @@
%ul.view-modes-menu
%li.two-up{data: {mode: 'two-up'}} 2-up
%li.swipe{data: {mode: 'swipe'}} Swipe
- %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin
\ No newline at end of file
+ %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index 5be8460e06..b36369b428 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -2,8 +2,7 @@
.commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
- = link_to_gfm truncate(commit.title, length: 40), project_commit_path(project, commit.id), class: "commit-row-message"
- %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
- = time_ago_in_words(commit.committed_date)
- ago
-
+ %span.str-truncated
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ .pull-right
+ #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml
new file mode 100644
index 0000000000..80f5be98f2
--- /dev/null
+++ b/app/views/projects/commits/_parallel_view.html.haml
@@ -0,0 +1,38 @@
+/ Side-by-side diff view
+- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
+- num_lines = old_lines.length
+
+%div.text-file
+ %table
+ - num_lines.times do |index|
+ - new_line = new_lines[index]
+ - old_line = old_lines[index]
+ %tr.line_holder.parallel
+ -# For old line
+ - if old_line.type == :file_created
+ %td.old_line= old_line.num
+ %td.line_content.parallel= "File was created"
+ - elsif old_line.type == :deleted
+ %td.old_line.old= old_line.num
+ %td.line_content{class: "parallel noteable_line old #{old_line.code}", "line_code" => old_line.code}= old_line.content
+ - else old_line.type == :no_change
+ %td.old_line= old_line.num
+ %td.line_content.parallel= old_line.content
+
+ -# For new line
+ - if new_line.type == :file_deleted
+ %td.new_line= new_line.num
+ %td.line_content.parallel= "File was deleted"
+ - elsif new_line.type == :added
+ %td.new_line.new= new_line.num
+ %td.line_content{class: "parallel noteable_line new #{new_line.code}", "line_code" => new_line.code}= new_line.content
+ - else new_line.type == :no_change
+ %td.new_line= new_line.num
+ %td.line_content.parallel= new_line.content
+
+ - if @reply_allowed
+ - comments1 = @line_notes.select { |n| n.line_code == old_line.code }.sort_by(&:created_at)
+ - comments2 = @line_notes.select { |n| n.line_code == new_line.code }.sort_by(&:created_at)
+ - unless comments1.empty? and comments2.empty?
+ = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2
+
diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml
index c724213878..ba83d2e5a0 100644
--- a/app/views/projects/commits/_text_file.html.haml
+++ b/app/views/projects/commits/_text_file.html.haml
@@ -1,6 +1,6 @@
- too_big = diff.diff.lines.count > 1000
- if too_big
- %a.supp_diff_link Diff suppressed. Click to show
+ %a.supp_diff_link Changes suppressed. Click to show
%table.text-file{class: "#{'hide' if too_big}"}
- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line|
@@ -21,3 +21,4 @@
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
- unless comments.empty?
= render "projects/notes/diff_notes_with_reply", notes: comments, line: line
+
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 27c8fa6da7..32c82edb24 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -3,7 +3,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.title "Recent commits to #{@project.name}:#{@ref}"
xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml"
xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html"
- xml.id project_commits_url(@project)
+ xml.id project_commits_url(@project, @ref)
xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any?
@commits.each do |commit|
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 723c5a1c34..3a4f304a25 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -9,7 +9,7 @@
%div{id: dom_id(@project)}
#commits-list= render "commits"
.clear
-.loading{ style: "display:none;"}
+= spinner
- if @commits.count == @limit
:javascript
diff --git a/app/views/projects/commits/show.js.haml b/app/views/projects/commits/show.js.haml
deleted file mode 100644
index 045c9dd83d..0000000000
--- a/app/views/projects/commits/show.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-:plain
- CommitsList.append(#{@commits.count}, "#{escape_javascript(render('projects/commits/commits'))}");
-
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index ca1ea4073e..da6157cf1b 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,21 +1,21 @@
-= form_tag project_compare_index_path(@project), method: :post do
- .clearfix
- .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'}
- .input-prepend
- %span.add-on.input-xpadding from
- = text_field_tag :from, params[:from], class: "span3 input-xpadding"
- = "..."
- .input-prepend
- %span.add-on.input-xpadding to
- = text_field_tag :to, params[:to], class: "span3 input-xpadding"
- .pull-left
-
- = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
- - if compare_to_mr_button?
- = link_to compare_mr_path, class: 'prepend-left-10' do
- %strong Make a merge request
+= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline' do
+ .clearfix.append-bottom-20
+ - 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'}
+ .form-group
+ .input-group.inline-input-group
+ %span.input-group-addon from
+ = text_field_tag :from, params[:from], class: "form-control"
+ = "..."
+ .form-group
+ .input-group.inline-input-group
+ %span.input-group-addon to
+ = text_field_tag :to, params[:to], class: "form-control"
+
+ = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ - if compare_to_mr_button?
+ = link_to compare_mr_path, class: 'prepend-left-10 btn' do
+ %strong Make a merge request
:javascript
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 4be62d3691..57331bff31 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -5,21 +5,32 @@
= render "form"
-- if @commits.size > 100
- .alert.alert-block
- %p
- %strong Warning! This comparison includes more than 100 commits.
- %p To preserve performance the line diff is not shown.
-
- if @commits.present?
%div.ui-box
.title
Commits (#{@commits.count})
- %ul.well-list= render Commit.decorate(@commits), project: @project
+ - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ %ul.well-list
+ - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
+ = render "projects/commits/inline_commit", commit: commit, project: @project
+ %li.warning-row.unstyled
+ other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+ - else
+ %ul.well-list= render Commit.decorate(@commits), project: @project
- - unless @diffs.empty?
- %h4 Diff
+ %h4 Changes
+ - if @diffs.present?
= render "projects/commits/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .bs-callout.bs-callout-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
+ - elsif @timeout
+ .bs-callout.bs-callout-danger
+ %h4 Number of changed files for this comparison is extremely large.
+ %p Use command line to browse through changes for this comparison.
+
+
- else
.light-well
%center
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
index a444b8b59a..89710d3a09 100644
--- a/app/views/projects/create.js.haml
+++ b/app/views/projects/create.js.haml
@@ -1,6 +1,10 @@
- if @project.saved?
- :plain
- location.href = "#{project_path(@project)}";
+ - if @project.import?
+ :plain
+ location.href = "#{import_project_path(@project)}";
+ - else
+ :plain
+ location.href = "#{project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 45f80ecd55..2b4f36fb4b 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -10,7 +10,7 @@
%i.icon-off
Disable
- else
- = link_to 'Remove', project_deploy_key_path(@project, deploy_key), confirm: 'You are going to remove deploy key. Are you sure?', method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
+ = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
= link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do
@@ -19,7 +19,6 @@
%p.light.prepend-top-10
- deploy_key.projects.map(&:name_with_namespace).each do |project_name|
- %span.label= project_name
+ %span.label.label-gray.deploy-project-label= project_name
%small.pull-right
- Created #{time_ago_in_words(deploy_key.created_at)} ago
-
+ Created #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index d49b220453..ebb92b36b4 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,21 +1,21 @@
%div
- = form_for [@project, @key], url: project_deploy_keys_path do |f|
+ = form_for [@project, @key], url: project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @key.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
- .control-group
- = f.label :title
- .controls= f.text_field :title, class: 'input-xlarge'
- .control-group
- = f.label :key
- .controls
+ .form-group
+ = f.label :title, class: "control-label"
+ .col-sm-10= f.text_field :title, class: 'form-control'
+ .form-group
+ = f.label :key, class: "control-label"
+ .col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_ssh_path
- = f.text_area :key, class: "input-xxlarge thin_area"
+ = f.text_area :key, class: "form-control thin_area", rows: 5
.form-actions
= f.submit 'Create', class: "btn-create btn"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 53d6e36c62..f50aeba337 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -12,7 +12,7 @@
%hr.clearfix
.row
- .span5.enabled-keys
+ .col-md-6.enabled-keys
%h5
%strong.cgreen Enabled deploy keys
for this project
@@ -20,8 +20,8 @@
= render @enabled_keys
- if @enabled_keys.blank?
.light-well
- %p.nothing_here_message Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
- .span5.available-keys
+ .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
+ .col-md-6.available-keys
%h5
%strong Deploy keys
from projects available to you
@@ -29,4 +29,4 @@
= render @available_keys
- if @available_keys.blank?
.light-well
- %p.nothing_here_message Deploy keys from projects you have access to will be displayed here
+ .nothing-here-block Deploy keys from projects you have access to will be displayed here
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
index 49566f83d2..c66e6bc69c 100644
--- a/app/views/projects/deploy_keys/show.html.haml
+++ b/app/views/projects/deploy_keys/show.html.haml
@@ -2,7 +2,7 @@
Deploy key:
= @key.title
%small
- created at
+ created on
= @key.created_at.stamp("Aug 21, 2011")
.back-link
= link_to project_deploy_keys_path(@project) do
@@ -10,4 +10,4 @@
%hr
%pre= @key.key
.pull-right
- = link_to 'Remove', project_deploy_key_path(@project, @key), confirm: 'Are you sure?', method: :delete, class: "btn-remove btn delete-key"
+ = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index d13ae937be..10674ccae4 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -7,95 +7,85 @@
%p.light Some settings, such as "Transfer Project", are hidden inside the danger area below
%hr
.form-holder
- = form_for(@project, remote: true) do |f|
+ = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f|
%fieldset
- .control-group.project_name_holder
- = f.label :name do
+ .form-group.project_name_holder
+ = f.label :name, class: 'control-label' do
Project name
- .controls
- = f.text_field :name, placeholder: "Example Project", class: "span5"
+ .col-sm-10
+ = f.text_field :name, placeholder: "Example Project", class: "form-control"
- .control-group
- = f.label :description do
+ .form-group
+ = f.label :description, class: 'control-label' do
Project description
%span.light (optional)
- .controls
- = f.text_area :description, placeholder: "Awesome project", class: "span5", rows: 3, maxlength: 250
+ .col-sm-10
+ = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250
- if @project.repository.exists? && @project.repository.branch_names.any?
- .control-group
- = f.label :default_branch, "Default Branch"
- .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'})
+ .form-group
+ = f.label :default_branch, "Default Branch", class: 'control-label'
+ .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
- - if can?(current_user, :change_public_mode, @project)
- %fieldset.public-mode
- %legend
- Public mode:
- .control-group
- = f.label :public, class: 'control-label' do
- %span Public access
- .controls
- = f.check_box :public
- %span.descr
- If checked, this project can be cloned
- %em without any
- authentication.
- It will also be listed on the #{link_to "public access directory", public_root_path}.
- %em Any
- user will have #{link_to "Guest", help_permissions_path} permissions on the repository.
+ = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project)
%fieldset.features
%legend
Labels:
- .control-group
+ .form-group
= f.label :label_list, "Labels", class: 'control-label'
- .controls
- = f.text_field :label_list, maxlength: 2000, class: "span5"
+ .col-sm-10
+ = f.text_field :label_list, maxlength: 2000, class: "form-control"
%p.hint Separate labels with commas.
%fieldset.features
%legend
Features:
- .control-group
+ .form-group
= f.label :issues_enabled, "Issues", class: 'control-label'
- .controls
- = f.check_box :issues_enabled
- %span.descr Lightweight issue tracking system for this project
+ .col-sm-10
+ .checkbox
+ = f.check_box :issues_enabled
+ %span.descr Lightweight issue tracking system for this project
- if Project.issues_tracker.values.count > 1
- .control-group
+ .form-group
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
- .controls= f.select(:issues_tracker, project_issues_trackers, {}, { disabled: !@project.issues_enabled })
+ .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled })
- .control-group
+ .form-group
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
- .controls= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?
+ .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control'
- .control-group
+ .form-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.
+ .col-sm-10
+ .checkbox
+ = f.check_box :merge_requests_enabled
+ %span.descr Submit changes to be merged upstream.
- .control-group
+ .form-group
= f.label :wiki_enabled, "Wiki", class: 'control-label'
- .controls
- = f.check_box :wiki_enabled
- %span.descr Pages for project documentation
+ .col-sm-10
+ .checkbox
+ = f.check_box :wiki_enabled
+ %span.descr Pages for project documentation
- .control-group
+ .form-group
= f.label :wall_enabled, "Wall", class: 'control-label'
- .controls
- = f.check_box :wall_enabled
- %span.descr Simple chat system for broadcasting inside project
+ .col-sm-10
+ .checkbox
+ = f.check_box :wall_enabled
+ %span.descr Simple chat system for broadcasting inside project
- .control-group
+ .form-group
= f.label :snippets_enabled, "Snippets", class: 'control-label'
- .controls
- = f.check_box :snippets_enabled
- %span.descr Share code pastes with others out of git repository
+ .col-sm-10
+ .checkbox
+ = f.check_box :snippets_enabled
+ %span.descr Share code pastes with others out of git repository
.form-actions
@@ -103,69 +93,105 @@
- %center.light.prepend-top-20.padded
- %h3
- %i.icon-warning-sign
- Dangerous settings
- %p Project settings below may result in data loss!
- = link_to '#', class: 'btn js-toggle-visibility-link' do
- Show it to me
- %i.icon-chevron-down
+ .danger-settings.js-toggle-container
+ .centered-light-block
+ %h3
+ %i.icon-warning-sign
+ Dangerous settings
+
+ %p Project settings below may result in data loss!
+ = link_to '#', class: 'btn js-toggle-button' do
+ Show it to me
+ %i.icon-chevron-down
+
+ .js-toggle-content.hide
+ - if can? current_user, :archive_project, @project
+ .ui-box.ui-box-danger
+ .title
+ - if @project.archived?
+ Unarchive project
+ - else
+ Archive project
+ .body
+ - if @project.archived?
+ %p
+ Unarchiving the project will mark its repository as active.
+ %br
+ The project can be committed to.
+ %br
+ %strong Once active this project shows up in the search and on the dashboard.
+ = link_to 'Unarchive', unarchive_project_path(@project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again." },
+ method: :post, class: "btn btn-remove"
+ - else
+ %p
+ Archiving the project will mark its repository as read-only.
+ %br
+ It is hidden from the dashboard and doesn't show up in searches.
+ %br
+ %strong Archived projects cannot be committed to!
+ = link_to 'Archive', archive_project_path(@project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-remove"
+ - else
+ .nothing-here-block Only the project owner can archive a project
+
+ - if can?(current_user, :change_namespace, @project)
+ .ui-box.ui-box-danger
+ .title Transfer project
+ .errors-holder
+ .form-holder
+ = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ .form-group
+ = f.label :namespace_id, class: 'control-label' do
+ %span Namespace
+ .col-sm-10
+ .form-group
+ = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
+ %ul
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %li You can only transfer the project to namespaces you manage.
+ %li You will need to update your local repositories to point to the new location.
+ .form-actions
+ = f.submit 'Transfer', class: "btn btn-remove"
+ - else
+ .nothing-here-block Only the project owner can transfer a project
- .js-toggle-visibility-container.hide
- - if can?(current_user, :change_namespace, @project)
.ui-box.ui-box-danger
- .title Transfer project
+ .title Rename repository
.errors-holder
.form-holder
- = form_for(@project, url: transfer_project_path(@project), remote: true, html: { class: 'transfer-project' }) do |f|
- .control-group
- = f.label :namespace_id do
- %span Namespace
- .controls
- .control-group
- = f.select :namespace_id, namespaces_options(@project.namespace_id), {prompt: 'Choose a project namespace'}, {class: 'chosen'}
+ = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ .form-group
+ = f.label :path, class: 'control-label' do
+ %span Path
+ .col-sm-9
+ .form-group
+ .input-group
+ = f.text_field :path, class: 'form-control'
+ %span.input-group-addon .git
%ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
+ %li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Transfer', class: "btn btn-remove"
- - else
- %p.nothing_here_message Only the project owner can transfer a project
+ = f.submit 'Rename', class: "btn btn-remove"
- .ui-box.ui-box-danger
- .title Rename repository
- .errors-holder
- .form-holder
- = form_for(@project) do |f|
- .control-group
- = f.label :path do
- %span Path
- .controls
- .control-group
- = f.text_field :path
- %ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- .form-actions
- = f.submit 'Rename', class: "btn btn-remove"
+ - if can?(current_user, :remove_project, @project)
+ .ui-box.ui-box-danger
+ .title Remove project
+ .body
+ %p
+ Removing the project will delete its repository and all related resources including issues, merge requests etc.
+ %br
+ %strong Removed projects cannot be restored!
- - if can?(current_user, :remove_project, @project)
- .ui-box.ui-box-danger
- .title Remove project
- .ui-box-body
- %p
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- %br
- %strong Removed projects cannot be restored!
-
- = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove"
- - else
- %p.nothing_here_message Only project owner can remove a project
+ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove"
+ - else
+ .nothing-here-block Only project owner can remove a project
.save-project-loader.hide
%center
- = image_tag "ajax_loader.gif"
- %h3 Saving project.
+ %h2
+ %i.icon-spinner.icon-spin
+ Saving project.
%p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/edit_tree/_diff.html.haml b/app/views/projects/edit_tree/_diff.html.haml
new file mode 100644
index 0000000000..cf044feb9a
--- /dev/null
+++ b/app/views/projects/edit_tree/_diff.html.haml
@@ -0,0 +1,13 @@
+%table.text-file
+ - each_diff_line(diff, 1) do |line, type, line_code, line_new, line_old, raw_line|
+ %tr.line_holder{ id: line_code, class: "#{type}" }
+ - if type == "match"
+ %td.old_line= "..."
+ %td.new_line= "..."
+ %td.line_content.matched= line
+ - else
+ %td.old_line
+ = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
+ %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
+ %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
+
diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/edit_tree/preview.html.haml
new file mode 100644
index 0000000000..fc6d3bfbc2
--- /dev/null
+++ b/app/views/projects/edit_tree/preview.html.haml
@@ -0,0 +1,26 @@
+.diff-file
+ .diff-content
+ - if gitlab_markdown?(@blob.name)
+ .file-content.wiki
+ = preserve do
+ = markdown(@content)
+ - elsif markup?(@blob.name)
+ .file-content.wiki
+ = raw GitHub::Markup.render(@blob.name, @content)
+ - else
+ .file-content.code
+ - unless @diff.empty?
+ %table.text-file
+ - @diff.each do |line, type, line_code, line_new, line_old, raw_line|
+ %tr.line_holder{ id: line_code, class: "#{type}" }
+ - if type == "match"
+ %td.old_line= "..."
+ %td.new_line= "..."
+ %td.line_content.matched= line
+ - else
+ %td.old_line
+ = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code
+ %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code
+ %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
+ - else
+ %p.nothing_here_message No changes.
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
index b1fb4f8637..93037ef958 100644
--- a/app/views/projects/edit_tree/show.html.haml
+++ b/app/views/projects/edit_tree/show.html.haml
@@ -1,8 +1,11 @@
%h3.page-title Edit mode
.file-editor
= form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
- .file-holder
+ .file-holder.file
.file-title
+ .btn-group.js-edit-mode.left-options
+ = link_to 'Edit', '#editor', class: 'active hover btn btn-tiny'
+ = link_to editing_preview_title(@blob.name), '#preview', class: 'btn btn-tiny', 'data-preview-url' => preview_project_edit_tree_path(@project, @id)
%i.icon-file
%span.file_name
= @path
@@ -11,27 +14,30 @@
%strong= @ref
%span.options
.btn-group.tree-btn-group
- = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-tiny btn-cancel", confirm: leave_edit_message
+ = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
.file-content.code
- %pre#editor= @blob.data
+ %pre.js-edit-mode-pane#editor= @blob.data
+ .js-edit-mode-pane#preview.hide
- .control-group.commit_message-group
+ .form-group.commit_message-group
= label_tag 'commit_message', class: "control-label" do
Commit message
- .controls
- = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3
+ .col-sm-10
+ = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', '',
+ placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control')}
.form-actions
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
.commit-button-annotation
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
.message
to branch
%strong= @ref
- = link_to "Cancel", project_blob_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message
+ = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
:javascript
- ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
+ ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
var ace_mode = "#{@blob.language.try(:ace_mode)}";
var editor = ace.edit("editor");
if (ace_mode) {
@@ -44,3 +50,28 @@
$("#file-content").val(editor.getValue());
$(".file-editor form").submit();
});
+
+ var editModePanes = $('.js-edit-mode-pane'),
+ editModeLinks = $('.js-edit-mode a');
+
+ editModeLinks.click(function(event) {
+ event.preventDefault();
+
+ var currentLink = $(this),
+ paneId = currentLink.attr('href'),
+ currentPane = editModePanes.filter(paneId);
+
+ editModeLinks.removeClass('active hover');
+ currentLink.addClass('active hover');
+ editModePanes.hide();
+
+ if (paneId == '#preview') {
+ $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) {
+ currentPane.empty().append(response);
+ currentPane.fadeIn(200);
+ })
+ } else {
+ currentPane.fadeIn(200);
+ editor.focus()
+ }
+ })
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 04fc0c3173..97dc73bce1 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,50 +1,34 @@
-%h3.page-title
- = @project.name_with_namespace
- .form-horizontal.pull-right
- = render "shared/clone_panel"
+= render "home_panel"
-- if @project.import? && !@project.imported
- .save-project-loader
- %center
- = image_tag "ajax_loader.gif"
- %h3 Importing repository.
- %p.monospace git clone --bare #{@project.import_url}
- %p Please wait while we import the repository for you. Refresh at will.
- :javascript
- new ProjectImport();
+%div.git-empty
+ %fieldset
+ %legend Git global setup:
+ %pre.dark
+ :preserve
+ git config --global user.name "#{git_user_name}"
+ git config --global user.email "#{git_user_email}"
-- else
- %div.git-empty
- %fieldset
- %legend Git global setup:
- %pre.dark
- :preserve
- git config --global user.name "#{git_user_name}"
- git config --global user.email "#{git_user_email}"
+ %fieldset
+ %legend Create Repository
+ %pre.dark
+ :preserve
+ mkdir #{@project.path}
+ cd #{@project.path}
+ git init
+ touch README
+ git add README
+ git commit -m 'first commit'
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git push -u origin master
- %fieldset
- %legend Create Repository
- %pre.dark
- :preserve
- mkdir #{@project.path}
- cd #{@project.path}
- git init
- touch README
- git add README
- git commit -m 'first commit'
- %span.clone= "git remote add origin #{@project.url_to_repo}"
- :preserve
- git push -u origin master
+ %fieldset
+ %legend Existing Git Repo?
+ %pre.dark
+ :preserve
+ cd existing_git_repo
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git push -u origin master
- %fieldset
- %legend Existing Git Repo?
- %pre.dark
- :preserve
- cd existing_git_repo
- %span.clone= "git remote add origin #{@project.url_to_repo}"
- :preserve
- git push -u origin master
-
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', @project, confirm: remove_project_message(@project), method: :delete, class: "btn btn-remove pull-right"
+- if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
index a1c109e5d6..bdd75de447 100644
--- a/app/views/projects/fork.html.haml
+++ b/app/views/projects/fork.html.haml
@@ -1,11 +1,11 @@
-.alert.alert-error.alert-block
+.alert.alert-danger.alert-block
%h4
%i.icon-code-fork
Fork Error!
%p
- You are trying to fork
+ You tried to fork
= link_to_project @project
- but it fails due to next reason:
+ but it failed for the following reason:
- if @forked_project && @forked_project.errors.any?
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index a21cb9e786..27348232ba 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,7 +1,8 @@
.loading-graph
%center
- .loading
- %h3.page-title Building repository graph.
+ %h3.page-title
+ %i.icon-spinner.icon-spin
+ Building repository graph.
%p Please wait a moment, this page will automatically refresh when ready.
.stat-graph
diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml
index 43e776e533..dcf6cacdce 100644
--- a/app/views/projects/graphs/show.js.haml
+++ b/app/views/projects/graphs/show.js.haml
@@ -16,4 +16,4 @@
})
- else
:plain
- $('.stat-graph').replaceWith('Failed to load graph
')
+ $('.stat-graph').replaceWith('Failed to load graph
')
diff --git a/app/views/projects/hooks/_data_ex.html.erb b/app/views/projects/hooks/_data_ex.html.erb
deleted file mode 100644
index 5092aaf675..0000000000
--- a/app/views/projects/hooks/_data_ex.html.erb
+++ /dev/null
@@ -1,44 +0,0 @@
-<% data_ex_str = <
-
- <%= raw Pygments::Lexer[:js].highlight(data_ex_str) %>
-
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index f748eb2929..866fd6f606 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,35 +1,68 @@
%h3.page-title
- Post-receive hooks
+ Web hooks
%p.light
- #{link_to "Post-receive hooks ", help_web_hooks_path, class: "vlink"} can be
- used for binding events when someone pushes to the repository.
+ #{link_to "Web hooks ", help_web_hooks_path, class: "vlink"} can be
+ used for binding events when something is happening within the project.
%hr.clearfix
-= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f|
+= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
- .alert.alert-error
+ .alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
- .control-group
- = f.label :url, "URL:"
- .controls
- = f.text_field :url, class: "text_field input-xxlarge input-xpadding", placeholder: 'http://example.com/trigger-ci.json'
-
- = f.submit "Add Web Hook", class: "btn btn-create"
-%hr
+ .form-group
+ = f.label :url, "URL", class: 'control-label'
+ .col-sm-10
+ = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10
+ %div
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ %div
+ = f.check_box :issues_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :issues_events, class: 'list-label' do
+ %strong Issues events
+ %p.light
+ This url will be triggered when an issue is created
+ %div
+ = f.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :merge_requests_events, class: 'list-label' do
+ %strong Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created
+ .form-actions
+ = f.submit "Add Web Hook", class: "btn btn-create"
-if @hooks.any?
.ui-box
.title
- Hooks (#{@hooks.count})
+ Web hooks (#{@hooks.count})
%ul.well-list
- @hooks.each do |hook|
%li
- %span.badge.badge-info POST
- →
- %span.monospace= hook.url
.pull-right
- = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped"
- = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small grouped"
+ = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped"
+ = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped"
+ .clearfix
+ %span.monospace= hook.url
+ %p
+ - %w(push_events tag_push_events issues_events merge_requests_events).each do |trigger|
+ - if hook.send(trigger)
+ %span.label.label-gray= trigger.titleize
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
new file mode 100644
index 0000000000..d11372be61
--- /dev/null
+++ b/app/views/projects/import.html.haml
@@ -0,0 +1,30 @@
+- if @project.import_in_progress?
+ .save-project-loader
+ %center
+ %h2
+ %i.icon-spinner.icon-spin
+ Import in progress.
+ %p.monospace git clone --bare #{@project.import_url}
+ %p Please wait while we import the repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
+
+- elsif @project.import_failed?
+ .save-project-loader
+ %center
+ %h2
+ Import failed. Retry?
+ %hr
+ - if can?(current_user, :admin_project, @project)
+ = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .bs-callout.bs-callout-info
+ This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 2 minutes. For big repositories, use a clone/push combination.
+ .form-actions
+ = f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 05cfb2788b..84703229fe 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,50 +1,51 @@
%div.issue-form-holder
%h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
- = form_for [@project, @issue] do |f|
+ %hr
+ - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
+ - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
+ .alert.alert-info.col-sm-10.col-sm-offset-2
+ ="Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe
+ = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f|
-if @issue.errors.any?
- .alert.alert-error
+ .alert.alert-danger
- @issue.errors.full_messages.each do |msg|
%span= msg
%br
- .ui-box.ui-box-show
- .ui-box-head
- .control-group
- = f.label :title do
- %strong= "Subject *"
- .controls
- = f.text_field :title, maxlength: 255, class: "input-xxlarge js-gfm-input", autofocus: true, required: true
- .ui-box-body
- .control-group
- .issue_assignee.pull-left
- = f.label :assignee_id do
- %i.icon-user
- Assign to
- .controls
- .pull-left
- = f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
- .pull-right
-
- = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
- .issue_milestone.pull-left
- = f.label :milestone_id do
- %i.icon-time
- Milestone
- .controls= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
+ .form-group
+ = f.label :title, class: 'control-label' do
+ %strong= 'Title *'
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: "form-control js-gfm-input", autofocus: true, required: true
+ .form-group
+ = f.label :description, 'Description', class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, class: "form-control js-gfm-input", rows: 14
+ %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ %hr
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
+ %i.icon-user
+ Assign to
+ .col-sm-10
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @issue.assignee_id)
+
+ = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
+ %i.icon-time
+ Milestone
+ .col-sm-10= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2'})
- .ui-box-bottom
- .control-group
- = f.label :label_list do
- %i.icon-tag
- Labels
- .controls
- = f.text_field :label_list, maxlength: 2000, class: "input-xxlarge"
- %p.hint Separate labels with commas.
+ .form-group
+ = f.label :label_list, class: 'control-label' do
+ %i.icon-tag
+ Labels
+ .col-sm-10
+ = f.text_field :label_list, maxlength: 2000, class: "form-control"
+ %p.hint Separate labels with commas.
- .control-group
- = f.label :description, "Details"
- .controls
- = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14
- %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.form-actions
@@ -90,6 +91,6 @@
});
$('.assign-to-me-link').on('click', function(e){
- $('#issue_assignee_id').val("#{current_user.id}").trigger("chosen:updated");
+ $('#issue_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 438cc02b47..f1e866c8e9 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -1,11 +1,31 @@
%ul.nav.nav-tabs
= nav_link(controller: :issues) do
- = link_to 'Browse Issues', project_issues_path(@project), class: "tab"
+ = link_to project_issues_path(@project), class: "tab" do
+ Browse Issues
+ - if current_controller?(:issues)
+ %span.badge.issue_counter #{@issues.total_count}
= nav_link(controller: :milestones) do
= link_to 'Milestones', project_milestones_path(@project), class: "tab"
= nav_link(controller: :labels) do
= link_to 'Labels', project_labels_path(@project), class: "tab"
- - if current_user
+
+ - if current_controller?(:issues)
+ - if current_user
+ %li
+ = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
+ %i.icon-rss
+
%li.pull-right
- = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- %i.icon-rss
+ .pull-right
+ = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
+ .append-right-10.hidden-xs.hidden-sm
+ = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
+ = hidden_field_tag :state, params['state']
+ = hidden_field_tag :scope, params['scope']
+ = hidden_field_tag :assignee_id, params['assignee_id']
+ = hidden_field_tag :milestone_id, params['milestone_id']
+ = hidden_field_tag :label_id, params['label_id']
+ - if can? current_user, :write_issue, @project
+ = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
+ %i.icon-plus
+ New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index effad718c8..3fc04c26cf 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -5,7 +5,8 @@
.issue-title
%span.light= "##{issue.iid}"
- = link_to_gfm truncate(issue.title, length: 100), project_issue_path(issue.project, issue), class: "row_title"
+ %span.str-truncated
+ = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title"
- if issue.closed?
%small.pull-right
= "CLOSED"
@@ -26,7 +27,7 @@
%i.icon-time
= issue.milestone.title
.pull-right
- %small updated #{time_ago_in_words(issue.updated_at)} ago
+ %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels
- issue.labels.each do |label|
@@ -37,10 +38,10 @@
.issue-actions
- if can? current_user, :modify_issue, issue
- if issue.closed?
- = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
+ = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue", remote: true
- else
- = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
- = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
+ = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue", remote: true
+ = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do
%i.icon-edit
Edit
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
new file mode 100644
index 0000000000..425dcb45dd
--- /dev/null
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -0,0 +1,24 @@
+= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
+ .row
+ .col-md-6
+ %strong.append-right-10
+ Assignee:
+
+ - if can?(current_user, :modify_issue, @issue)
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id)
+ - elsif issue.assignee
+ = link_to_member(@project, @issue.assignee)
+ - else
+ None
+
+ .col-md-6.text-right
+ %strong.append-right-10
+ Milestone:
+ - if can?(current_user, :modify_issue, @issue)
+ = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
+ = hidden_field_tag :issue_context
+ = f.submit class: 'btn'
+ - elsif issue.milestone
+ = link_to issue.milestone.title, project_milestone_path
+ - else
+ None
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 427d653313..e85ff3a786 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,93 +1,76 @@
-.ui-box
- .title
+.append-bottom-10
+ .check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .clearfix
- .issues_bulk_update.hide
- = form_tag bulk_update_project_issues_path(@project), method: :post do
- %span Update selected issues with
- = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status")
- = select_tag('update[assignee_id]', options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]), prompt: "Assignee")
- = select_tag('update[milestone_id]', options_from_collection_for_select(project_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone")
- = hidden_field_tag 'update[issues_ids]', []
- = hidden_field_tag :status, params[:status]
- = button_tag "Save", class: "btn update_selected_issues btn-small btn-save"
- .issues-filters
- %span Filter by
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-tags
- %span.light labels:
- - if params[:label_name].present?
- %strong= params[:label_name]
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(label_name: nil) do
- Any
- - issue_label_names.each do |label_name|
- %li
- = link_to project_filter_path(label_name: label_name) do
- %span{class: "label #{label_css_class(label_name)}"}
- %i.icon-tag
- = label_name
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @project.team.members.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
+ .issues-filters
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(assignee_id: nil) do
+ Any
+ = link_to project_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-time
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(milestone_id: nil) do
+ Any
+ = link_to project_filter_path(milestone_id: 0) do
+ None (backlog)
+ - project_active_milestones.each do |milestone|
+ %li
+ = link_to project_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+ .pull-right
+ = render 'shared/sort_dropdown'
+ .clearfix
+ .issues_bulk_update.hide
+ = form_tag bulk_update_project_issues_path(@project), method: :post do
+ = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status")
+ = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ = hidden_field_tag 'update[issues_ids]', []
+ = hidden_field_tag :status, params[:status]
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+
+.ui-box
%ul.well-list.issues-list
= render @issues
- if @issues.blank?
%li
- %h4.nothing_here_message No issues to show
+ .nothing-here-block No issues to show
- if @issues.present?
.pull-right
%span.issue_counter #{@issues.total_count}
issues for this filter
- = paginate @issues, remote: true, theme: "gitlab"
+ = paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 81594b4972..51a8c911af 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,23 +1,7 @@
= render "head"
-.issues_content
- %h3.page-title
- Issues
- %span (#{@issues.total_count})
- .pull-right
- .span6
- - if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-right", title: "New Issue", id: "new_issue_link" do
- %i.icon-plus
- New Issue
- = form_tag project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: 'pull-right' do
- = hidden_field_tag :status, params[:status], id: 'search_status'
- = hidden_field_tag :assignee_id, params[:assignee_id], id: 'search_assignee_id'
- = hidden_field_tag :milestone_id, params[:milestone_id], id: 'search_milestone_id'
- = hidden_field_tag :label_name, params[:label_name], id: 'search_label_name'
- = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'input-xpadding issue_search input-xlarge append-right-10 search-text-input' }
-
.row
- .span3
- = render 'shared/project_filter', project_entities_path: project_issues_path(@project)
- .span9.issues-holder
+ .col-md-3
+ = render 'shared/project_filter', project_entities_path: project_issues_path(@project),
+ labels: true, redirect: 'issues'
+ .col-md-9.issues-holder
= render "issues"
diff --git a/app/views/projects/issues/index.js.haml b/app/views/projects/issues/index.js.haml
deleted file mode 100644
index 1be6a64f53..0000000000
--- a/app/views/projects/issues/index.js.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-:plain
- $('.issues-holder').html("#{escape_javascript(render('issues'))}");
- History.replaceState({path: "#{request.url}"}, document.title, "#{request.url}");
- Issues.reload();
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index d08a8af2bb..b6d3a8edf4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,74 +1,76 @@
%h3.page-title
Issue ##{@issue.iid}
- %small
- created at
- = @issue.created_at.stamp("Aug 21, 2011")
-
- %span.pull-right
+ %span.pull-right.issue-btn-group
- if can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), class: "btn grouped", title: "New Issue", id: "new_issue_link" do
+ = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
%i.icon-plus
New Issue
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
+ = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- else
- = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
+ = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
- = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
+ = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
%i.icon-edit
Edit
-.pull-right
- .span3#votes= render 'votes/votes_block', votable: @issue
+.clearfix
+ .votes-holder
+ #votes= render 'votes/votes_block', votable: @issue
-.back-link
- = link_to project_issues_path(@project) do
- ← To issues list
+ .back-link
+ = link_to project_issues_path(@project) do
+ ← To issues list
+ %span.milestone-nav-link
+ - if @issue.milestone
+ |
+ %span.light Milestone
+ = link_to project_milestone_path(@project, @issue.milestone) do
+ = @issue.milestone.title
-
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
+.issue-box{ class: issue_box_class(@issue) }
+ .state.clearfix
+ .state-label.col-sm-2.col-xs-12
- if @issue.closed?
- .state-label.state-label-red Closed
- = gfm escape_once(@issue.title)
+ Closed
+ - else
+ Open
- .ui-box-body
- %cite.cgray
- Created by #{link_to_member(@project, @issue.author)}
- - if @issue.assignee
- \ and currently assigned to #{link_to_member(@project, @issue.assignee)}
+ %span.creator.col-sm-9.col-xs-12
+ Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
- - if @issue.milestone
- - milestone = @issue.milestone
- %cite.cgray and attached to milestone
- %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
-
- .pull-right
- - @issue.labels.each do |label|
- %span{class: "label #{label_css_class(label.name)}"}
- %i.icon-tag
- = label.name
-
+ %h4.title
+ = gfm escape_once(@issue.title)
- if @issue.description.present?
- .ui-box-bottom
+ .description
.wiki
= preserve do
= markdown @issue.description
+ .context
+ %cite.cgray
+ = render partial: 'issue_context', locals: { issue: @issue }
+
- content_for :note_actions do
- if can?(current_user, :modify_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
+ = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- else
- = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
+ = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
.participants
%cite.cgray #{@issue.participants.count} participants
- @issue.participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
+ .issue-show-labels.pull-right
+ - @issue.labels.each do |label|
+ %span{class: "label #{label_css_class(label.name)}"}
+ %i.icon-tag
+ = label.name
+
+
.voting_notes#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 7f66022a2d..5199e9fc61 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -2,3 +2,9 @@
- if @issue.valid?
:plain
$("##{dom_id(@issue)}").fadeOut();
+- elsif params[:issue_context]
+ $('.issue-box .context').effect('highlight');
+ - if @issue.milestone
+ $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}")
+ - else
+ $('.milestone-nav-link').html('')
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index a058d09a1c..329cf9ceba 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -7,4 +7,4 @@
- else
.light-well
- %h3.nothing_here_message Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
+ .nothing-here-block Add first label to your issues or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index ce72756303..290a15e266 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,59 +1,56 @@
-= form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |f|
- -if @merge_request.errors.any?
- .alert.alert-error
- %ul
- - @merge_request.errors.full_messages.each do |msg|
- %li= msg
+= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f|
+ .row
+ .col-sm-2
+ .col-sm-10
+ - if @repository.contribution_guide && !@merge_request.persisted?
+ - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
+ .alert.alert-info
+ Please review the
+ %strong #{link_to "guidelines for contribution", contribution_guide_url}
+ to this repository.
- .merge-request-branches
- .row
- .span5
- .clearfix
- .pull-left
- = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span3'})
- .pull-left
-
- %i.icon-code-fork
- = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span2'})
- .mr_source_commit.prepend-top-10
- .span2
- %h2.merge-request-angle.light
- %i.icon-long-arrow-right
- .span5
- .clearfix
- .pull-left
- - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
- = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span3'})
- .pull-left
-
- %i.icon-code-fork
- = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span2'})
- .mr_target_commit.prepend-top-10
+ -if @merge_request.errors.any?
+ .alert.alert-danger
+ - @merge_request.errors.full_messages.each do |msg|
+ %div= msg
- %hr
.merge-request-form-info
- .control-group
- = f.label :title do
+ .form-group
+ = f.label :title, class: 'control-label' do
%strong= "Title *"
- .controls= f.text_field :title, class: "input-xxlarge pad js-gfm-input", maxlength: 255, rows: 5, required: true
- .control-group
- .left
- = f.label :assignee_id do
+ .col-sm-10= f.text_field :title, class: "form-control pad js-gfm-input", maxlength: 255, rows: 5, required: true
+ .form-group
+ = f.label :description, "Description", class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, class: "form-control js-gfm-input", rows: 14
+ %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ %hr
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
%i.icon-user
Assign to
- .controls= f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
- .left
- = f.label :milestone_id do
+ .col-sm-10
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id)
+
+ = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
%i.icon-time
Milestone
- .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
- .control-group
- = f.label :description, "Description"
- .controls
- = f.text_area :description, class: "input-xxlarge js-gfm-input", rows: 14
- %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
+ - if @merge_request.persisted? # Only allow labels on edit to avoid fork vs upstream repo labels issue
+ .form-group
+ = f.label :label_list, class: 'control-label' do
+ %i.icon-tag
+ Labels
+ .col-sm-10
+ = f.text_field :label_list, maxlength: 2000, class: "form-control"
+ %p.hint Separate labels with commas.
+
.form-actions
- if @merge_request.new_record?
= f.submit 'Submit merge request', class: "btn btn-create"
@@ -68,20 +65,36 @@
:javascript
disableButtonIfEmptyField("#merge_request_title", ".btn-save");
-
- var source_branch = $("#merge_request_source_branch")
- , target_branch = $("#merge_request_target_branch")
- , target_project = $("#merge_request_target_project_id");
-
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
-
- target_project.on("change", function() {
- $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
- });
- source_branch.on("change", function() {
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
- });
- target_branch.on("change", function() {
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
+ $('.assign-to-me-link').on('click', function(e){
+ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
+ e.preventDefault();
});
+
+ $("#merge_request_label_list")
+ .bind( "keydown", function( event ) {
+ if ( event.keyCode === $.ui.keyCode.TAB &&
+ $( this ).data( "autocomplete" ).menu.active ) {
+ event.preventDefault();
+ }
+ })
+ .bind("click", function(event) {
+ $(this).autocomplete("search", "");
+ })
+ .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/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 933d78bcbf..c9a80ec22e 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -10,14 +10,10 @@
%span.pull-right
- if merge_request.for_fork?
%span.light
- = "#{merge_request.source_project.path_with_namespace}"
- = "#{merge_request.source_branch}"
- %i.icon-angle-right.light
- = "#{merge_request.target_branch}"
- - else
- = "#{merge_request.source_branch}"
- %i.icon-angle-right.light
- = "#{merge_request.target_branch}"
+ #{merge_request.source_project_namespace}:
+ = truncate merge_request.source_branch, length: 25
+ %i.icon-angle-right.light
+ = merge_request.target_branch
.merge-request-info
- if merge_request.author
authored by #{link_to_member(merge_request.source_project, merge_request.author)}
@@ -34,4 +30,10 @@
.pull-right
- %small updated #{time_ago_in_words(merge_request.updated_at)} ago
+ %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
+
+ .merge-request-labels
+ - merge_request.labels.each do |label|
+ %span{class: "label #{label_css_class(label.name)}"}
+ %i.icon-tag
+ = label.name
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
new file mode 100644
index 0000000000..a8b774a3cd
--- /dev/null
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -0,0 +1,84 @@
+%h3.page-title Compare branches for new Merge Request
+%hr
+
+= form_for [@project, @merge_request], url: new_project_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
+ .hide.alert.alert-danger.mr-compare-errors
+ .merge-request-branches.row
+ .col-md-6
+ .panel.panel-default
+ .panel-heading
+ %strong Source branch
+ .panel-body
+ = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? })
+
+ = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'})
+ .panel-footer
+ .mr_source_commit
+
+ .col-md-6
+ .panel.panel-default
+ .panel-heading
+ %strong Target branch
+ .panel-body
+ - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
+ = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
+
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'})
+ .panel-footer
+ .mr_target_commit
+
+ -if @merge_request.errors.any?
+ .alert.alert-danger
+ - @merge_request.errors.full_messages.each do |msg|
+ %div= msg
+
+ - if @merge_request.source_branch.present? && @merge_request.target_branch.present?
+ .light-well
+ %center
+ %h4
+ There isn't anything to merge.
+ %p.slead
+ - if @merge_request.source_branch == @merge_request.target_branch
+ You'll need to use different branch names to get a valid comparison.
+ - else
+ %span.label-branch #{@merge_request.source_branch}
+ and
+ %span.label-branch #{@merge_request.target_branch}
+ are the same.
+
+
+ %hr
+ = f.submit 'Compare branches', class: "btn btn-primary mr-compare-btn"
+
+:javascript
+ var source_branch = $("#merge_request_source_branch")
+ , target_branch = $("#merge_request_target_branch")
+ , target_project = $("#merge_request_target_project_id");
+
+ $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
+ $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
+
+ target_project.on("change", function() {
+ $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
+ });
+ source_branch.on("change", function() {
+ $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
+ $(".mr-compare-errors").fadeOut();
+ $(".mr-compare-btn").enable();
+ });
+ target_branch.on("change", function() {
+ $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
+ $(".mr-compare-errors").fadeOut();
+ $(".mr-compare-btn").enable();
+ });
+
+
+:coffeescript
+
+ $(".merge-request-form").on 'submit', ->
+ if $("#merge_request_source_branch").val() is "" or $('#merge_request_target_branch').val() is ""
+ $(".mr-compare-errors").html("You must select source and target branch to proceed")
+ $(".mr-compare-errors").fadeIn()
+ event.preventDefault()
+ return
+
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
new file mode 100644
index 0000000000..b5479be708
--- /dev/null
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -0,0 +1,82 @@
+%h3.page-title
+ New merge request
+%p.slead
+ From
+ %strong.monospace
+ #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
+ into
+ %strong.monospace
+ #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+
+ %span.pull-right
+ = link_to 'Change branches', new_project_merge_request_path(@project)
+
+= form_for [@project, @merge_request], html: { class: "merge-request-form" } do |f|
+ .panel.panel-default
+
+ .panel-body
+ .form-group
+ .light
+ = f.label :title do
+ = "Title *"
+ = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true
+ .form-group
+ .light
+ = f.label :description, "Description"
+ = f.text_area :description, class: "form-control js-gfm-input", rows: 10
+ %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id do
+ %i.icon-user
+ Assign to
+ %div
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
+
+ = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id do
+ %i.icon-time
+ Milestone
+ %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
+ .panel-footer
+ - if @target_repo.contribution_guide
+ - contribution_guide_url = project_blob_path(@target_project, tree_join(@target_repo.root_ref, @target_repo.contribution_guide.name))
+ %p
+ Please review the
+ %strong #{link_to "guidelines for contribution", contribution_guide_url}
+ to this repository.
+ = f.hidden_field :source_project_id
+ = f.hidden_field :target_project_id
+ = f.hidden_field :target_branch
+ = f.hidden_field :source_branch
+ = f.submit 'Submit merge request', class: "btn btn-create"
+
+.mr-compare
+ %div.ui-box
+ .title
+ Commits (#{@commits.count})
+ - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ %ul.well-list
+ - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
+ = render "projects/commits/inline_commit", commit: commit, project: @project
+ %li.warning-row.unstyled
+ other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
+ - else
+ %ul.well-list= render Commit.decorate(@commits), project: @project
+
+ %h4 Changes
+ - if @diffs.present?
+ = render "projects/commits/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .bs-callout.bs-callout-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
+
+
+:javascript
+ $('.assign-to-me-link').on('click', function(e){
+ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
+ e.preventDefault();
+ });
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 47c14abded..193c600f77 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -2,10 +2,9 @@
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/show/mr_box"
- = render "projects/merge_requests/show/mr_accept"
- - if @merge_request.source_project.gitlab_ci?
- = render "projects/merge_requests/show/mr_ci"
+ = render "projects/merge_requests/show/state_widget"
= render "projects/merge_requests/show/commits"
+ = render "projects/merge_requests/show/participants"
- if @commits.present?
%ul.nav.nav-tabs
@@ -16,7 +15,14 @@
%li.diffs-tab{data: {action: 'diffs'}}
= link_to diffs_project_merge_request_path(@project, @merge_request) do
%i.icon-list-alt
- Diff
+ Changes
+
+ - content_for :note_actions do
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ - unless @merge_request.closed? || @merge_request.merged?
+ = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link", title: "Close merge request"
+ - if @merge_request.closed?
+ = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
.notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
index ec4d7f2121..8372afa61b 100644
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -1,7 +1,2 @@
:plain
- $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}");
- var mrTitle = $('#merge_request_title');
-
- if(mrTitle.val().length == 0) {
- mrTitle.val("#{params[:ref].titleize}");
- }
+ $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
index f4e2886ee4..f7ede0ded5 100644
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -1,2 +1,2 @@
:plain
- $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}");
+ $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
diff --git a/app/views/projects/merge_requests/commits.js.haml b/app/views/projects/merge_requests/commits.js.haml
deleted file mode 100644
index 923b1ea032..0000000000
--- a/app/views/projects/merge_requests/commits.js.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-:plain
- merge_request.$(".commits").html("#{escape_javascript(render(partial: "commits"))}");
-
-
diff --git a/app/views/projects/merge_requests/diffs.js.haml b/app/views/projects/merge_requests/diffs.js.haml
deleted file mode 100644
index 2964f0ec46..0000000000
--- a/app/views/projects/merge_requests/diffs.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- merge_request.$(".diffs").html("#{escape_javascript(render(partial: "projects/merge_requests/show/diffs"))}");
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index e256ee2153..12a72edb22 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,67 +5,68 @@
%h3.page-title
Merge Requests
%span (#{@merge_requests.total_count})
-
-
+%hr
.row
- .span3
- = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project)
- .span9
+ .col-md-3
+ = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
+ labels: true, redirect: 'merge_requests'
+ .col-md-9
+ .mr-filters.append-bottom-10
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(assignee_id: nil) do
+ Any
+ = link_to project_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.icon-time
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(milestone_id: nil) do
+ Any
+ = link_to project_filter_path(milestone_id: 0) do
+ None (backlog)
+ - project_active_milestones.each do |milestone|
+ %li
+ = link_to project_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+
+ .pull-right
+ = render 'shared/sort_dropdown'
+
.ui-box
- .title
- .mr-filters
- %span Filter by
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @project.team.members.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %i.icon-time
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
%ul.well-list.mr-list
= render @merge_requests
- if @merge_requests.blank?
%li
- %h4.nothing_here_message No merge requests to show
+ .nothing-here-block No merge requests to show
- if @merge_requests.present?
.pull-right
%span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index c962811a3e..b9c466657d 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -2,16 +2,22 @@
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/mr_box"
- .alert.alert-error
- %h5
- %i.icon-exclamation-sign
- We cannot find
- %span.label-branch= @merge_request.source_branch
- or
- %span.label-branch= @merge_request.target_branch
- branches in the repository.
- %p
- Maybe it was removed or never pushed.
+ .alert.alert-danger
%p
+ We cannot render this merge request properly because
+ - if @merge_request.for_fork? && !@merge_request.source_project
+ fork project was removed
+ - elsif !@merge_request.source_branch_exists?
+ %span.label.label-inverse= @merge_request.source_branch
+ does not exist in
+ %span.label.label-info= @merge_request.source_project_path
+ - elsif !@merge_request.target_branch_exists?
+ %span.label.label-inverse= @merge_request.target_branch
+ does not exist in
+ %span.label.label-info= @merge_request.target_project_path
+ - else
+ of internal error
+
+ %strong
Please close Merge Request or change branches with existing one
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index 8ee0e1a8d4..c24e591672 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,3 +1,4 @@
-%h3.page-title New Merge Request
-%hr
-= render 'form'
+- if @commits.present?
+ = render 'new_submit'
+- else
+ = render 'new_compare'
diff --git a/app/views/projects/merge_requests/show.js.haml b/app/views/projects/merge_requests/show.js.haml
deleted file mode 100644
index 2ce6eb6329..0000000000
--- a/app/views/projects/merge_requests/show.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- merge_request.$(".notes").html("#{escape_javascript(render "notes/notes_with_form")}");
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 7b0e67053a..f69146e1a3 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -12,19 +12,19 @@
8 of #{@commits.count} commits displayed.
%strong
%a.show-all-commits Click here to show all
- %ul.all-commits.hide.well-list
- - @commits.each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
+ - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ %ul.all-commits.hide.well-list
+ - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit|
+ = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
+ %li
+ other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden top prevent performance issues.
+ - else
+ %ul.all-commits.hide.well-list
+ - @commits.each do |commit|
+ = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
- else
%ul.well-list
- @commits.each do |commit|
= render "projects/commits/commit", commit: commit, project: @merge_request.source_project
-- else
- %h4.nothing_here_message
- Nothing to merge from
- %span.label-branch #{@merge_request.source_branch}
- to
- %span.label-branch #{@merge_request.target_branch}
- %br
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
new file mode 100644
index 0000000000..5c6734fd24
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -0,0 +1,24 @@
+= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
+ .row
+ .col-md-6
+ %strong.append-right-10
+ Assignee:
+
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id)
+ - elsif merge_request.assignee
+ = link_to_member(@project, @merge_request.assignee)
+ - else
+ None
+
+ .col-md-6.text-right
+ %strong.append-right-10
+ Milestone:
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
+ = hidden_field_tag :merge_request_context
+ = f.submit class: 'btn'
+ - elsif merge_request.milestone
+ = link_to merge_request.milestone.title, project_milestone_path
+ - else
+ None
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 25f6380485..eb63b68106 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,10 +1,12 @@
-- if @merge_request.valid_diffs?
+- if @merge_request_diff.collected?
= render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
-- elsif @merge_request.broken_diffs?
- %h4.nothing_here_message
- Can't load diff.
- You can
- = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink"
- instead.
+- elsif @merge_request_diff.empty?
+ .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
- %h4.nothing_here_message Nothing to merge
+ .bs-callout.bs-callout-warning
+ %h4
+ Changes view for this comparison is extremely large.
+ %p
+ You can
+ = link_to "download it", project_merge_request_path(@merge_request.target_project, @merge_request, format: :diff), class: "vlink"
+ instead.
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 030ac285f2..9540453ce3 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -1,42 +1,44 @@
%div#modal_merge_info.modal.hide
- .modal-header
- %a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3 How to merge
- .modal-body
- - if @merge_request.for_fork?
- - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
- - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
- %p
- %strong Step 1.
- Checkout target branch and get recent objects from GitLab
- Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote}
- remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote}
- %pre.dark
- :preserve
- git checkout #{target_remote} #{@merge_request.target_branch}
- git fetch #{source_remote}
- %p
- %strong Step 2.
- Merge source branch into target branch and push changes to GitLab
- %pre.dark
- :preserve
- git merge #{source_remote}/#{@merge_request.source_branch}
- git push #{target_remote} #{@merge_request.target_branch}
- - else
- %p
- %strong Step 1.
- Checkout target branch and get recent objects from GitLab
- %pre.dark
- :preserve
- git checkout #{@merge_request.target_branch}
- git fetch origin
- %p
- %strong Step 2.
- Merge source branch into target branch and push changes to GitLab
- %pre.dark
- :preserve
- git merge origin/#{@merge_request.source_branch}
- git push origin #{@merge_request.target_branch}
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 How to merge
+ .modal-body
+ - if @merge_request.for_fork?
+ - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
+ - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
+ %p
+ %strong Step 1.
+ Checkout the branch we are going to merge and pull in the code
+ %pre.dark
+ :preserve
+ git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch}
+ git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ %p
+ %strong Step 2.
+ Merge the branch and push the changes to GitLab
+ %pre.dark
+ :preserve
+ git checkout #{@merge_request.target_branch}
+ git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch}
+ git push origin #{@merge_request.target_branch}
+ - else
+ %p
+ %strong Step 1.
+ Update the repo and checkout the branch we are going to merge
+ %pre.dark
+ :preserve
+ git fetch origin
+ git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
+ %p
+ %strong Step 2.
+ Merge the branch and push the changes to GitLab
+ %pre.dark
+ :preserve
+ git checkout #{@merge_request.target_branch}
+ git merge --no-ff #{@merge_request.source_branch}
+ git push origin #{@merge_request.target_branch}
:javascript
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 299e1bc1e0..1276489c2d 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -1,53 +1,75 @@
- unless @allowed_to_merge
- .alert
- %strong You don't have permission to merge this MR
+ - if @project.archived?
+ %p
+ %strong Archived projects cannot be committed to!
+ - else
+ .automerge_widget.cannot_be_merged.hide
+ %strong This can't be merged automatically, even if it could be merged you don't have the permission to do so.
+ .automerge_widget.can_be_merged.hide
+ %strong This can be merged automatically but you don't have the permission to do so.
- if @show_merge_controls
- .automerge_widget.can_be_merged{style: "display:none"}
- .alert.alert-success
- %span
- = form_for [:automerge, @project, @merge_request], remote: true, method: :get do |f|
+ .automerge_widget.can_be_merged.hide
+ .clearfix
+ = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
+ %h4
+ You can accept this request automatically.
+ %div
+ If you still want to do it manually -
+ %strong
+ = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+ for instructions.
+
+ .js-toggle-container
%p
- You can accept this request automatically.
- If you still want to do it manually -
+ If you want to modify merge commit message -
%strong
- = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- for instructions
- .accept_group
+ = link_to "click here", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
+ .js-toggle-content.hide
+ .form-group
+ = label_tag :merge_commit_message, "Commit message", class: 'control-label'
+ .col-sm-10
+ = render 'shared/commit_message_container', {textarea: text_area_tag(:merge_commit_message,
+ @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true)}
+ %p.hint
+ The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
+
+ .accept-group
+ .pull-left
= f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
- - unless @merge_request.disallow_source_branch_removal?
- .remove_branch_holder
- = label_tag :should_remove_source_branch, class: "checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source-branch
- .clearfix
+ - unless @merge_request.disallow_source_branch_removal?
+ .remove_branch_holder.pull-left
+ = label_tag :should_remove_source_branch, class: "checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source-branch
- .automerge_widget.no_satellite{style: "display:none"}
- .alert.alert-error
+ .automerge_widget.no_satellite.hide
+ %p
%span
%strong This repository does not have satellite. Ask an administrator to fix this issue
- .automerge_widget.cannot_be_merged{style: "display:none"}
- .alert.alert-disabled
- %span
- = link_to "Show how to merge", "#modal_merge_info", class: "how_to_merge_link btn padded", title: "How To Merge", "data-toggle" => "modal"
-
- %strong This request can't be merged with GitLab. You should do it manually
+ .automerge_widget.cannot_be_merged.hide
+ %h4
+ This request can't be merged with GitLab.
+ %p
+ You should do it manually with
+ %strong
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal"
.automerge_widget.unchecked
- .alert
+ %p
%strong
- %i.icon-refresh
+ %i.icon-spinner.icon-spin
Checking for ability to automatically merge…
- .automerge_widget.already_cannot_be_merged{style: "display:none"}
- .alert.alert-info
- %strong This merge request already can not be merged. Try to reload page.
+ .automerge_widget.already_cannot_be_merged.hide
+ %p
+ %strong This merge request can not be merged. Try to reload the page.
.merge-in-progress.hide
- %span.cgray
- %i.icon-refresh.icon-spin
+ %p
+ %i.icon-spinner.icon-spin
Merge is in progress. Please wait. Page will be automatically reloaded.
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index e1d0c7c11e..435e916c6d 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,49 +1,25 @@
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
- = gfm escape_once(@merge_request.title)
+.issue-box{ class: issue_box_class(@merge_request) }
+ .state.clearfix
+ %span.state-label.col-sm-2.col-xs-12
- if @merge_request.merged?
- .state-label.state-label-green
- %i.icon-ok
- Merged
+ Merged
- elsif @merge_request.closed?
- .state-label.state-label-red
- Closed
+ Closed
+ - else
+ Open
- .ui-box-body
- %div
- %cite.cgray
- Created on #{@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
- %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
+ %span.creator.col-sm-9.col-xs-12
+ Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
+ %h4.title
+ = gfm escape_once(@merge_request.title)
- if @merge_request.description.present?
- .ui-box-bottom
+ .description
.wiki
= preserve do
= markdown @merge_request.description
- - if @merge_request.closed?
- .ui-box-bottom.alert-error
- %span
- %i.icon-remove
- Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
- %span #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
- - if @merge_request.merged?
- .ui-box-bottom.alert-success
- %span
- %i.icon-ok
- Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
- #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
- - if !@closes_issues.empty? && @merge_request.opened?
- .ui-box-bottom.alert-info
- %span
- %i.icon-ok
- Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
- = succeed '.' do
- != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)
+ .context
+ %cite.cgray
+ = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index 9b15c4526e..507a9e507f 100644
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -1,35 +1,29 @@
- if @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)
+ %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)
+ %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)
+ %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
- %strong
- %i.icon-refresh
- Checking for CI status for #{@merge_request.last_commit_short_sha}
+ %strong
+ %i.icon-spinner
+ 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
-
+ %i.icon-remove
+ %strong Cannot connect to the CI server. Please check your settings and try again.
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 096a916764..2c905413bc 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,22 +1,11 @@
%h3.page-title
- = "Merge Request ##{@merge_request.iid}:"
-
- -if @merge_request.for_fork?
- %span.label-branch
- %span.label-project= truncate(@merge_request.source_project.path_with_namespace, length: 25)
- #{@merge_request.source_branch}
- →
- %span.label-branch= @merge_request.target_branch
- - else
- %span.label-branch= @merge_request.source_branch
- →
- %span.label-branch= @merge_request.target_branch
+ = "Merge Request ##{@merge_request.iid}"
- %span.pull-right
+ %span.pull-right.issue-btn-group
- if can?(current_user, :modify_merge_request, @merge_request)
- - if @merge_request.opened?
- .left.btn-group
- %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
+ - if @merge_request.open?
+ .btn-group.pull-left
+ %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt
Download as
%span.caret
@@ -24,15 +13,30 @@
%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: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
+ = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
- = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do
+ = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
%i.icon-edit
Edit
+ - if @merge_request.closed?
+ = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
-.pull-right
- .span3#votes= render 'votes/votes_block', votable: @merge_request
+.votes-holder.hidden-sm.hidden-xs
+ #votes= render 'votes/votes_block', votable: @merge_request
.back-link
= link_to project_merge_requests_path(@project) do
← To merge requests
+
+ %span.prepend-left-20.monospace
+ -if @merge_request.for_fork?
+ %span
+ %strong
+ #{truncate(@merge_request.source_project_path, length: 25)}:
+ #{@merge_request.source_branch}
+ →
+ %span= @merge_request.target_branch
+ - else
+ %span= @merge_request.source_branch
+ →
+ %span= @merge_request.target_branch
diff --git a/app/views/projects/merge_requests/show/_no_accept.html.haml b/app/views/projects/merge_requests/show/_no_accept.html.haml
new file mode 100644
index 0000000000..423fcd48e2
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_no_accept.html.haml
@@ -0,0 +1,14 @@
+%h4
+ Can't be merged
+%p
+ This merge request can not be accepted because branch
+ - unless @merge_request.source_branch_exists?
+ %span.label.label-inverse= @merge_request.source_branch
+ does not exist in
+ %span.label.label-info= @merge_request.source_project_path
+ - else
+ %span.label.label-inverse= @merge_request.target_branch
+ does not exist in
+ %span.label.label-info= @merge_request.target_project_path
+ %br
+ %strong Please close this merge request or change branches with existing one
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
new file mode 100644
index 0000000000..0dabd965e5
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_participants.html.haml
@@ -0,0 +1,11 @@
+.participants
+ %cite.cgray #{@merge_request.participants.count} participants
+ - @merge_request.participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+
+ .merge-request-show-labels.pull-right
+ - @merge_request.labels.each do |label|
+ %span{class: "label #{label_css_class(label.name)}"}
+ %i.icon-tag
+ = label.name
+
diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
new file mode 100644
index 0000000000..f8f8b71f21
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
@@ -0,0 +1,17 @@
+- if @source_branch.blank?
+ Source branch has been removed
+
+- elsif @allowed_to_remove_source_branch && @merge_request.merged?
+ .remove_source_branch_widget
+ %p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
+ = link_to project_branch_path(@merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-small remove_source_branch" do
+ %i.icon-remove
+ Remove Source Branch
+
+ .remove_source_branch_widget.failed.hide
+ Failed to remove source branch '#{@merge_request.source_branch}'
+
+ .remove_source_branch_in_progress.hide
+ %i.icon-refresh.icon-spin
+
+ Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
new file mode 100644
index 0000000000..80fe540489
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -0,0 +1,41 @@
+.panel.mr-state-widget.panel-default
+ - if @merge_request.source_project.gitlab_ci? && @commits.any?
+ .panel-heading
+ = render "projects/merge_requests/show/mr_ci"
+ .panel-body
+ - if @merge_request.open?
+ - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
+ = render "projects/merge_requests/show/mr_accept"
+ - else
+ = render "projects/merge_requests/show/no_accept"
+
+ - if @merge_request.closed?
+ %h4
+ Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+ %p Changes was not merged into target branch
+
+ - if @merge_request.merged?
+ %h4
+ Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+ = render "projects/merge_requests/show/remove_source_branch"
+
+ - unless @commits.any?
+ %h4 Nothing to merge
+ %p
+ Nothing to merge from
+ %span.label-branch #{@merge_request.source_branch}
+ to
+ %span.label-branch #{@merge_request.target_branch}
+ %br
+ Try to use different branches or push new code.
+
+ - if !@closes_issues.empty? && @merge_request.open?
+ .panel-footer
+ %span
+ %i.icon-ok
+ Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
+ = succeed '.' do
+ != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)
+
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
new file mode 100644
index 0000000000..6452cc6382
--- /dev/null
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -0,0 +1,2 @@
+- if params[:merge_request_context]
+ $('.issue-box .context').effect('highlight');
diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml
index dfccb586ec..ca21b3bc0d 100644
--- a/app/views/projects/merge_requests/update_branches.js.haml
+++ b/app/views/projects/merge_requests/update_branches.js.haml
@@ -1,5 +1,9 @@
:plain
$(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
- $(".target_branch").trigger("chosen:updated");
+
+ $('select.target_branch').select2({
+ width: 'resolve',
+ dropdownAutoWidth: true
+ });
+
$(".mr_target_commit").html("");
- $(".target_branch").trigger("change");
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index b2fd1a9284..d770bb5b37 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -7,27 +7,27 @@
= form_for [@project, @milestone], html: {class: "new_milestone form-horizontal"} do |f|
-if @milestone.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @milestone.errors.full_messages.each do |msg|
%li= msg
.row
- .span6
- .control-group
+ .col-md-6
+ .form-group
= f.label :title, "Title", class: "control-label"
- .controls
- = f.text_field :title, maxlength: 255, class: "input-xlarge"
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: "form-control"
%p.hint Required
- .control-group
+ .form-group
= f.label :description, "Description", class: "control-label"
- .controls
- = f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10
+ .col-sm-10
+ = f.text_area :description, maxlength: 2000, class: "form-control", rows: 10
%p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
- .span6
- .control-group
+ .col-md-6
+ .form-group
= f.label :due_date, "Due Date", class: "control-label"
- .controls= f.hidden_field :due_date
- .controls
+ .col-sm-10= f.hidden_field :due_date
+ .col-sm-10
.datepicker
.form-actions
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
index 21939ad013..c277dcc536 100644
--- a/app/views/projects/milestones/_issues.html.haml
+++ b/app/views/projects/milestones/_issues.html.haml
@@ -4,7 +4,7 @@
- issues.each do |issue|
%li
= link_to [@project, issue] do
- %span.badge{class: issue.closed? ? 'badge-important' : 'badge-info'} ##{issue.iid}
+ %span.label{class: issue.closed? ? 'label-danger' : 'label-info'} ##{issue.iid}
= link_to_gfm truncate(issue.title, length: 40), [@project, issue]
- if issue.assignee
.pull-right
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index 8e30a42a60..faa35a25f2 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -1,5 +1,5 @@
%li
= link_to [@project, merge_request] do
- %span.badge.badge-info ##{merge_request.iid}
+ %span.label.label-info ##{merge_request.iid}
–
= link_to_gfm truncate(merge_request.title, length: 60), [@project, merge_request]
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index bc3368b765..5579659d60 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,7 +1,7 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
+ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do
%i.icon-edit
Edit
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-remove"
@@ -24,4 +24,4 @@
%span.light #{milestone.percent_complete}% complete
.progress.progress-info
- .bar{style: "width: #{milestone.percent_complete}%;"}
+ .progress-bar{style: "width: #{milestone.percent_complete}%;"}
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index ddb46bcb5c..3537650ad4 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -8,7 +8,7 @@
New Milestone
.row
- .span3
+ .col-md-3.hidden-sm
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if (params[:f] == "active" || !params[:f]))}
= link_to project_milestones_path(@project, f: "active") do
@@ -19,13 +19,13 @@
%li{class: ("active" if params[:f] == "all")}
= link_to project_milestones_path(@project, f: "all") do
All
- .span9
+ .col-md-9
.ui-box
%ul.well-list
= render @milestones
- if @milestones.blank?
%li
- %h3.nothing_here_message No milestones to show
+ .nothing-here-block No milestones to show
= paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index a5bd7a2901..5c5df46d33 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -5,13 +5,13 @@
= @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: "btn grouped" do
+ = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
%i.icon-edit
Edit
- if @milestone.active?
- = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove"
+ = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-remove btn-grouped"
- else
- = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn"
+ = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped"
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
@@ -22,17 +22,20 @@
← To milestones list
-.ui-box.ui-box-show
- .ui-box-head
- %h4.box-title
+.issue-box{ class: issue_box_class(@milestone) }
+ .state
+ %span.state-label
- if @milestone.closed?
- .state-label.state-label-red Closed
+ Closed
- elsif @milestone.expired?
- .state-label.state-label-red Expired
+ Expired
+ - else
+ Open
- = gfm escape_once(@milestone.title)
+ %h4.title
+ = gfm escape_once(@milestone.title)
- .ui-box-body
+ .context
%p
Progress:
#{@milestone.closed_items_count} closed
@@ -40,14 +43,13 @@
#{@milestone.open_items_count} open
%span.pull-right= @milestone.expires_at
.progress.progress-info
- .bar{style: "width: #{@milestone.percent_complete}%;"}
-
+ .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
- if @milestone.description.present?
- .ui-box-bottom
- = preserve do
- = markdown @milestone.description
-
+ .description
+ .wiki
+ = preserve do
+ = markdown @milestone.description
%ul.nav.nav-tabs
%li.active
@@ -64,30 +66,30 @@
%span.badge= @users.count
.pull-right
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
+ = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-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 btn-small edit-milestone-link grouped"
+ = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
- .span4
+ .col-md-4
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned)
- .span4
+ .col-md-4
= render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned)
- .span4
+ .col-md-4
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed)
.tab-pane#tab-merge-requests
.row
- .span6
+ .col-md-6
.ui-box
.title Open
%ul.well-list
- @merge_requests.opened.each do |merge_request|
= render 'merge_request', merge_request: merge_request
- .span6
+ .col-md-6
.ui-box
.title Closed
%ul.well-list
@@ -98,7 +100,7 @@
%ul.bordered-list
- @users.each do |user|
%li
- = link_to user, title: user.name, class: "dark" do
+ = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 2790ed6f59..d61ff789b6 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,23 +1,15 @@
-.clearfix
- .pull-left
+.row.append-bottom-20
+ .col-md-2.append-bottom-10
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
- .pull-left
- = form_tag project_network_path(@project, @id), method: :get do |f|
- .control-group
- = label_tag :filter_ref, "Begin with the selected commit", class: 'control-label light'
- .controls
- = check_box_tag :filter_ref, 1, @options[:filter_ref]
- - @options.each do |key, value|
- = hidden_field_tag(key, value, id: nil) unless key == "filter_ref"
-
- .search.pull-right
- = form_tag project_network_path(@project, @id), method: :get do |f|
- .control-group
- = label_tag :search , "Looking for commit:", class: 'control-label light'
- .controls
- = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input input-xlarge"
- = button_tag type: 'submit', class: 'btn vtop' do
- %i.icon-search
- - @options.each do |key, value|
- = hidden_field_tag(key, value, id: nil) unless key == "extended_sha1"
+ .col-md-10
+ = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = label_tag :extended_sha1 , "Looking for", class: 'light inline-label'
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input form-control input-mx-250"
+ = button_tag type: 'submit', class: 'btn btn-success' do
+ %i.icon-search
+ .inline.prepend-left-20
+ .checkbox.light
+ = label_tag :filter_ref do
+ = check_box_tag :filter_ref, 1, @options[:filter_ref]
+ %span Begin with the selected commit
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 492f77341f..da0cfa84c2 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -3,7 +3,7 @@
.tip
You can move around the graph by using the arrow keys.
.network-graph
- .loading.loading-gray
+ = spinner
:javascript
new Network({
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 466a63dee8..5d5637c158 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,69 +3,77 @@
= render 'projects/errors'
.project-edit-content
- = form_for @project, remote: true do |f|
- .control-group.project-name-holder
- = f.label :name do
+ = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f|
+ .form-group.project-name-holder
+ = f.label :name, class: 'control-label' do
%strong Project name
- .controls
- = f.text_field :name, placeholder: "Example Project", class: "input-xlarge", tabindex: 1, autofocus: true
- %span.help-inline
- = link_to "#", class: 'js-toggle-visibility-link' do
- %span Customize repository name?
-
- .control-group.js-toggle-visibility-container.hide
- = f.label :path do
- %span Repository name
- .controls
- .input-append
- = f.text_field :path
- %span.add-on .git
-
+ .col-sm-10
+ = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true
- if current_user.can_select_namespace?
- .control-group
- = f.label :namespace_id do
+ .form-group
+ = f.label :namespace_id, class: 'control-label' do
%span Namespace
- .controls
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen', tabindex: 2}
+ .col-sm-10
+ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
- .control-group
- .controls
- = link_to "#", class: 'appear-link' do
- %i.icon-upload-alt
- %span Import existing repository?
- .control-group.appear-data.import-url-data
- = f.label :import_url do
- %span Import existing repo
- .controls
- = f.text_field :import_url, class: 'input-xlarge', placeholder: 'https://github.com/randx/six.git'
- .light
- URL must be cloneable
- .control-group
- = f.label :description do
+ %hr
+ .js-toggle-container
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = link_to "#", class: 'js-toggle-button' do
+ %i.icon-edit
+ %span Customize repository name?
+ .js-toggle-content.hide
+ .form-group
+ = f.label :path, class: 'control-label' do
+ %span Repository name
+ .col-sm-10
+ .input-group
+ = f.text_field :path, class: 'form-control'
+ %span.input-group-addon .git
+
+ .js-toggle-container
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = link_to "#", class: 'js-toggle-button' do
+ %i.icon-upload-alt
+ %span Import existing repository?
+ .js-toggle-content.hide
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .bs-callout.bs-callout-info
+ This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 2 minutes. For big repositories, use a clone/push combination.
+ %hr
+
+ .form-group
+ = f.label :description, class: 'control-label' do
Description
%span.light (optional)
- .controls
- = f.text_area :description, placeholder: "Awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3
- .control-group.project-public-holder
- = f.label :public do
- %span Public project
- .controls
- = f.check_box :public, { checked: gitlab_config.default_projects_features.public }, true, false
- %span.help-inline Make project visible to everyone
+ .col-sm-10
+ = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3
+ = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
.form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
- if current_user.can_create_group?
.pull-right
- .controls.light
+ .light
Need a group for several dependent projects?
= link_to new_group_path, class: "btn btn-tiny" do
Create a group
.save-project-loader.hide
%center
- = image_tag "ajax_loader.gif"
- %h3 Creating project & repository.
+ %h2
+ %i.icon-spinner.icon-spin
+ Creating project & repository.
%p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml
new file mode 100644
index 0000000000..9ecbbe7508
--- /dev/null
+++ b/app/views/projects/new_tree/show.html.haml
@@ -0,0 +1,54 @@
+%h3.page-title New file
+%hr
+.file-editor
+ = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do
+ .form-group.commit_message-group
+ = label_tag 'file_name', class: "control-label" do
+ File name
+ .col-sm-10
+ .input-group
+ %span.input-group-addon
+ = @path[-1] == "/" ? @path : @path + "/"
+ = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control'
+ %span.input-group-addon
+ on
+ %span= @ref
+
+ .form-group.commit_message-group
+ = label_tag :encoding, class: "control-label" do
+ Encoding
+ .col-sm-10
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+
+ .form-group.commit_message-group
+ = label_tag 'commit_message', class: "control-label" do
+ Commit message
+ .col-sm-10
+ = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
+ params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control')}
+
+ .file-holder
+ .file-title
+ %i.icon-file
+ .file-content.code
+ %pre#editor= params[:content]
+
+ .form-actions
+ = hidden_field_tag 'content', '', id: "file-content"
+ .commit-button-annotation
+ = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
+ .message
+ to branch
+ %strong= @ref
+ = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
+
+:javascript
+ ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
+ var editor = ace.edit("editor");
+
+ disableButtonIfEmptyField("#commit_message", ".js-commit-button");
+
+ $(".js-commit-button").click(function(){
+ $("#file-content").val(editor.getValue());
+ $(".file-editor form").submit();
+ });
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 9537ab18ca..9acadc6a14 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -1,7 +1,7 @@
- note = notes.first # example note
-# Check if line want not changed since comment was left
- if !defined?(line) || line == note.diff_line
- %tr.notes_holder
+ %tr.notes_holder.js-toggle-content
%td.notes_line{ colspan: 2 }
%span.btn.disabled
%i.icon-comment
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
new file mode 100644
index 0000000000..399ce30d1a
--- /dev/null
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -0,0 +1,33 @@
+- note1 = notes1.first # example note
+- note2 = notes2.first # example note
+-# Check if line want not changed since comment was left
+/- if !defined?(line) || line == note.diff_line
+%tr.notes_holder.js-toggle-content
+ - if note1
+ %td.notes_line
+ %span.btn.disabled
+ %i.icon-comment
+ = notes1.count
+ %td.notes_content
+ %ul.notes{ rel: note1.discussion_id }
+ = render notes1
+
+ = render "projects/notes/discussion_reply_button", note: note1
+ - else
+ %td= ""
+ %td= ""
+
+ - if note2
+ %td.notes_line
+ %span.btn.disabled
+ %i.icon-comment
+ = notes2.count
+ %td.notes_content
+ %ul.notes{ rel: note2.discussion_id }
+ = render notes2
+
+ = render "projects/notes/discussion_reply_button", note: note2
+ - else
+ %td= ""
+ %td= ""
+
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index e0aad0cc95..78793eb860 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,13 +1,10 @@
- note = discussion_notes.first
-.discussion.js-details-container.js-toggler-container.open{ class: note.discussion_id }
+.discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header
.discussion-actions
- = link_to "javascript:;", class: "js-details-target turn-on js-toggler-target" do
- %i.icon-eye-close
- Hide discussion
- = link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do
- %i.icon-eye-open
- Show discussion
+ = link_to "#", class: "js-toggle-button" do
+ %i.icon-chevron-up
+ Show/hide discussion
= image_tag avatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
@@ -32,32 +29,18 @@
last updated by
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- = time_ago_in_words(last_note.updated_at)
- ago
- .discussion-body
+ #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
+ .discussion-body.js-toggle-content
- if note.for_diff_line?
- if note.active?
- .content
- .file= render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note
+ = render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note
- else
= link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion'
%div.hide.outdated-discussion
- .content
- .notes{ rel: discussion_notes.first.discussion_id }
- = render discussion_notes
-
+ .notes{ rel: discussion_notes.first.discussion_id }
+ = render discussion_notes
- else
- .content
- .notes{ rel: discussion_notes.first.discussion_id }
- = render discussion_notes
- = render "projects/notes/discussion_reply_button", note: discussion_notes.first
-
- -# will be shown when the other one is hidden
- .discussion-hidden.content.hide
- .note
- %em Hidden discussion.
- = link_to "javascript:;", class: "js-details-target js-toggler-target" do
- %i.icon-eye-open
- Show
-
+ .notes{ rel: discussion_notes.first.discussion_id }
+ = render discussion_notes
+ = render "projects/notes/discussion_reply_button", note: discussion_notes.first
diff --git a/app/views/projects/notes/_discussion_diff.html.haml b/app/views/projects/notes/_discussion_diff.html.haml
index c3f41a1b6b..26c5494f46 100644
--- a/app/views/projects/notes/_discussion_diff.html.haml
+++ b/app/views/projects/notes/_discussion_diff.html.haml
@@ -1,24 +1,26 @@
- diff = note.diff
-.header
- - if diff.deleted_file
- %span= diff.old_path
- - else
- %span= diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- %br/
-.content
- %table
- - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old|
- %tr.line_holder{ id: line_code }
- - if type == "match"
- %td.old_line= "..."
- %td.new_line= "..."
- %td.line_content.matched= line
- - else
- %td.old_line= raw(type == "new" ? " " : line_old)
- %td.new_line= raw(type == "old" ? " " : line_new)
- %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} "
+- if diff
+ .diff-file
+ .diff-header
+ - if diff.deleted_file
+ %span= diff.old_path
+ - else
+ %span= diff.new_path
+ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
+ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
+ %br/
+ .diff-content
+ %table
+ - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old|
+ %tr.line_holder{ id: line_code }
+ - if type == "match"
+ %td.old_line= "..."
+ %td.new_line= "..."
+ %td.line_content.matched= line
+ - else
+ %td.old_line= raw(type == "new" ? " " : line_old)
+ %td.new_line= raw(type == "old" ? " " : line_new)
+ %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} "
- - if line_code == note.line_code
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
+ - if line_code == note.line_code
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 7add292183..3db551e993 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,5 +1,4 @@
-= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" } do |f|
-
+= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f|
= note_target_fields
= f.hidden_field :commit_id
= f.hidden_field :line_code
@@ -7,10 +6,12 @@
= f.hidden_field :noteable_type
.note_text_and_preview.js-toggler-container
- %a.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Preview", data: {url: preview_project_notes_path(@project)} }
+ %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} }
%i.icon-eye-open
- %a.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;", title: "Edit" }
+ Preview
+ %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" }
%i.icon-edit
+ Write
= f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on'
.note_preview.js-note-preview.turn-off
@@ -21,17 +22,17 @@
.note-form-actions
.buttons
- = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
.note-form-option
- %a.choose-btn.btn.btn-small.js-choose-note-attachment-button
+ %a.choose-btn.btn.js-choose-note-attachment-button
%i.icon-paper-clip
%span Choose File ...
%span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hide"
+ = f.file_field :attachment, class: "js-note-attachment-input hidden"
.clearfix
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index e56e9153c2..2fd8cb6d48 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -10,7 +10,7 @@
%i.icon-edit
Edit
- = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do
+ = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
%i.icon-trash.cred
Remove
= image_tag avatar_icon(note.author_email), class: "avatar s32"
@@ -23,7 +23,7 @@
%i.icon-thumbs-up
\+1
- if note.downvote?
- %span.vote.downvote.label.label-error
+ %span.vote.downvote.label.label-danger
%i.icon-thumbs-down
\-1
@@ -31,13 +31,13 @@
.note-body
.note-text
= preserve do
- = markdown(note.note)
+ = markdown(note.note, {no_header_anchors: true})
.note-edit-form
- = form_for note, url: project_note_path(@project, note), method: :put, remote: true do |f|
+ = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
= f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
- .form-actions
+ .form-actions.clearfix
= f.submit 'Save changes', class: "btn btn-primary btn-save"
.note-form-option
@@ -46,7 +46,7 @@
%span Choose File ...
%span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hide"
+ = f.file_field :attachment, class: "js-note-attachment-input hidden"
= link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
@@ -54,13 +54,13 @@
- if note.attachment.url
.note-attachment
- if note.attachment.image?
- = link_to note.attachment.url, target: '_blank' do
- = image_tag note.attachment.url, class: 'note-image-attach'
+ = link_to note.attachment.secure_url, target: '_blank' do
+ = image_tag note.attachment.secure_url, class: 'note-image-attach'
.attachment.pull-right
= link_to note.attachment.secure_url, target: "_blank" do
%i.icon-paper-clip
= note.attachment_identifier
= link_to delete_attachment_project_note_path(@project, note),
- title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do
+ title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
%i.icon-trash.cred
.clear
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index ac8901fe70..ca60dd239b 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -4,7 +4,7 @@
- if note_for_main_target?(note)
= render discussion_notes
- else
- = render 'discussion', discussion_notes: discussion_notes
+ = render 'projects/notes/discussion', discussion_notes: discussion_notes
- else
- @notes.each do |note|
- next unless note.author
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index ac28c7432e..052661962e 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,4 +1,5 @@
-%ul#notes-list.notes
+%ul#notes-list.notes.main-notes-list
+ = render "projects/notes/notes"
.js-notes-busy
.js-main-target-form
@@ -6,4 +7,4 @@
= render "projects/notes/form"
:javascript
- NoteList.init("#{@target_id}", "#{@target_type}", "#{project_notes_path(@project)}");
+ new Notes("#{project_notes_path(target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
diff --git a/app/views/projects/notes/create.js.haml b/app/views/projects/notes/create.js.haml
deleted file mode 100644
index c113b3482e..0000000000
--- a/app/views/projects/notes/create.js.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-- if @note.valid?
- var noteHtml = "#{escape_javascript(render @note)}";
-
- - if note_for_main_target?(@note)
- NoteList.appendNewNote(#{@note.id}, noteHtml);
- - else
- :plain
- var firstDiscussionNoteHtml = "#{escape_javascript(render "projects/notes/diff_notes_with_reply", notes: [@note])}";
- NoteList.appendNewDiscussionNote("#{@note.discussion_id}",
- firstDiscussionNoteHtml,
- noteHtml);
-
-- else
- var errorsHtml = "#{escape_javascript(render 'projects/notes/form_errors', note: @note)}";
- - if note_for_main_target?(@note)
- NoteList.errorsOnForm(errorsHtml);
- - else
- NoteList.errorsOnForm(errorsHtml, "#{@note.discussion_id}");
diff --git a/app/views/projects/notes/index.js.haml b/app/views/projects/notes/index.js.haml
deleted file mode 100644
index 6c4ed20349..0000000000
--- a/app/views/projects/notes/index.js.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- unless @notes.blank?
- var notesHtml = "#{escape_javascript(render 'projects/notes/notes')}";
- - new_note_ids = @notes.map(&:id)
- NoteList.setContent(#{new_note_ids}, notesHtml);
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 8930ec4b30..4a6e8943a9 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,29 +1,30 @@
= render "projects/commits/head"
.row
- .span3
+ .col-md-3
= render "projects/branches/filter"
- .span9
- .alert.alert-info
+ .col-md-9
+ .bs-callout.bs-callout-info
%p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}.
%p This ability allows:
%ul
%li keep stable branches secured
%li forced code review before merge to protected branches
- %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined_link"}
+ %li prevents branch from force push
+ %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
- if can? current_user, :admin_project, @project
- = form_for [@project, @protected_branch] do |f|
+ = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f|
-if @protected_branch.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @protected_branch.errors.full_messages.each do |msg|
%li= msg
- .entry.clearfix
- = f.label :name, "Branch"
- .span3
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})
-
+ .form-group
+ = f.label :name, "Branch", class: 'control-label'
+ .col-sm-10
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ .form-actions
= f.submit 'Protect', class: "btn-create btn"
- unless @branches.empty?
%h5 Already Protected:
@@ -39,15 +40,13 @@
%i.icon-lock
.pull-right
- if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project, branch], confirm: 'Branch will be writable for developers. Are you sure?', method: :delete, class: "btn btn-remove btn-small"
+ = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
- if commit = branch.commit
= link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
= commit.short_id
%span.light
= gfm escape_once(truncate(commit.title, length: 40))
- %span
- = time_ago_in_words(commit.committed_date)
- ago
+ #{time_ago_with_tooltip(commit.committed_date)}
- else
(branch was removed from repository)
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 213c54f5f7..e7343e0997 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -5,5 +5,5 @@
:plain
var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}");
- row.find("td.tree_time_ago").html('#{escape_javascript time_ago_in_words(commit.committed_date)} ago');
+ row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
new file mode 100644
index 0000000000..88c1cfa28e
--- /dev/null
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -0,0 +1,37 @@
+- ref = ref || nil
+- btn_class = btn_class || ''
+- split_button = split_button || false
+- if split_button == true
+ %span.btn-group{class: btn_class}
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ %i.icon-download-alt
+ %span Download zip
+ %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %span.caret
+ %span.sr-only
+ Select Archive Format
+ %ul.dropdown-menu{ role: 'menu' }
+ %li
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ %i.icon-download-alt
+ %span Download zip
+ %li
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ %i.icon-download-alt
+ %span Download tar.gz
+ %li
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ %i.icon-download-alt
+ %span Download tar.bz2
+ %li
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ %i.icon-download-alt
+ %span Download tar
+- else
+ %span.btn-group{class: btn_class}
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ %i.icon-download-alt
+ %span zip
+ = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
+ %i.icon-download-alt
+ %span tar.gz
\ No newline at end of file
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index 6e537d2959..c77ffff43f 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -15,5 +15,4 @@
= gfm escape_once(truncate(commit.title, length: 40))
%td
%span.pull-right.cgray
- = time_ago_in_words(commit.committed_date)
- ago
+ = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml
index 679b4211cd..fd35397857 100644
--- a/app/views/projects/repositories/stats.html.haml
+++ b/app/views/projects/repositories/stats.html.haml
@@ -1,6 +1,6 @@
= render "projects/commits/head"
.row
- .span6
+ .col-md-6
%div#activity-chart.chart
%hr
%p
@@ -14,7 +14,7 @@
%span= @stats.authors_count
- .span6
+ .col-md-6
%h4 Top 50 Committers:
%ol.styled
- @stats.authors[0...50].each do |author|
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index a099193cb6..70b4537fd3 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -1,11 +1,6 @@
%h3.page-title
- - if @service.activated?
- %span.cgreen
- %i.icon-circle
- - else
- %span.cgray
- %i.icon-circle-blank
= @service.title
+ = boolean_to_icon @service.activated?
%p= @service.description
@@ -15,17 +10,17 @@
%hr
-= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put) do |f|
+= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- if @service.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @service.errors.full_messages.each do |msg|
%li= msg
- .control-group
+ .form-group
= f.label :active, "Active", class: "control-label"
- .controls
+ .col-sm-10
= f.check_box :active
- @service.fields.each do |field|
@@ -33,11 +28,13 @@
- type = field[:type]
- placeholder = field[:placeholder]
- .control-group
+ .form-group
= f.label name, class: "control-label"
- .controls
+ .col-sm-10
- if type == 'text'
- = f.text_field name, class: "input-xlarge", placeholder: placeholder
+ = f.text_field name, class: "form-control", placeholder: placeholder
+ - elsif type == 'textarea'
+ = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= f.check_box name
@@ -45,4 +42,4 @@
= f.submit 'Save', class: 'btn btn-save'
- if @service.valid? && @service.activated? && @service.can_test?
- = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn btn-small'
+ = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn'
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index 82b85a18ac..7271dd830c 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -1,17 +1,13 @@
-%h3.page-title Services
-%p.light Services allow you to integrate GitLab with other applications
+%h3.page-title Project services
+%p.light Project services allow you to integrate GitLab with other applications
%hr
%ul.bordered-list
- - @services.each do |service|
+ - @services.sort_by(&:title).each do |service|
%li
%h4
- - if service.activated?
- %span.cgreen
- %i.icon-circle
- - else
- %span.cgray
- %i.icon-circle-blank
= link_to edit_project_service_path(@project, service.to_param) do
= service.title
+ .pull-right
+ = boolean_to_icon service.activated?
%p= service.description
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index cad072a83d..7e9f1122aa 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,70 +1,57 @@
-.project-home-panel
- .row
- .span4
- %h4.project-home-title
- = @project.name_with_namespace
- - if @project.public
- %span.public-label Public
- - else
- %span.public-label Private
-
- .span8
- .project-home-dropdown
- = render "dropdown"
- .form-horizontal
- = render "shared/clone_panel"
-
- .project-home-extra.clearfix
- .project-home-desc
- - if @project.description.present?
- = @project.description
- - if can?(current_user, :admin_project, @project)
- –
- %strong= link_to 'Edit', edit_project_path
-
- .project-home-links
- = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
- = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)
- = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)
- %span.light.prepend-left-20= repository_size
+= render "home_panel"
.row
- .span9
+ .col-md-9
= render "events/event_last_push", event: @last_push
= render 'shared/event_filter'
.content_list
- .loading.hide
- .span3
+ = spinner
+ .col-md-3.project-side.hidden-sm.hidden-xs
.clearfix
+ - if @project.archived?
+ .alert.alert-warning
+ %h4
+ %i.icon-warning-sign
+ Archived project!
+ %p Repository is read-only
+
+
- if @project.forked_from_project
.alert.alert-success
- %i.icon-code-fork
+ %i.icon-code-fork.project-fork-icon
Forked from:
+ %br
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user.already_forked?(@project)
= link_to project_path(current_user.fork_of(@project)), class: 'btn btn-block' do
- %i.icon-ok
- Already forked
+ %i.icon-compass
+ Go to fork
- else
= link_to fork_project_path(@project), title: "Fork", class: "btn btn-block", method: "POST" do
%i.icon-code-fork
Fork repository
- if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project), class: "btn btn-block" do
- %i.icon-download-alt
- %span Download
- %br
- .light-well
+ = render 'projects/repositories/download_archive', btn_class: 'btn-block btn-group-justified', split_button: true
+
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
+ Compare code
+
+ - if @repository.readme
+ - readme = @repository.readme
+ = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do
+ = readme.name
+
+ .prepend-top-10
%p
%span.light Created on
#{@project.created_at.stamp('Aug 22, 2013')}
%p
%span.light Owned by
- if @project.group
- #{link_to @project.group.name, @project.group} Group
+ #{link_to @project.group.name, @project.group} group
- else
#{link_to @project.owner_name, @project.owner}
diff --git a/app/views/projects/show.js.haml b/app/views/projects/show.js.haml
deleted file mode 100644
index 511f278929..0000000000
--- a/app/views/projects/show.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- Pager.append(#{@events.count}, "#{escape_javascript(render(@events))}");
diff --git a/app/views/projects/snippets/_blob.html.haml b/app/views/projects/snippets/_blob.html.haml
index f14a2bd4ec..af326a1a99 100644
--- a/app/views/projects/snippets/_blob.html.haml
+++ b/app/views/projects/snippets/_blob.html.haml
@@ -6,10 +6,5 @@
.btn-group.tree-btn-group.pull-right
- if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user
= link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-tiny", title: 'Edit Snippet'
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", 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
+ = link_to "Raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank"
+ = render 'snippets/blob_content'
diff --git a/app/views/projects/snippets/_form.html.haml b/app/views/projects/snippets/_form.html.haml
index d414ee2d1e..866346990d 100644
--- a/app/views/projects/snippets/_form.html.haml
+++ b/app/views/projects/snippets/_form.html.haml
@@ -2,26 +2,23 @@
= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
%hr
.snippet-form-holder
- = form_for [@project, @snippet], as: :project_snippet, url: url do |f|
+ = form_for [@project, @snippet], as: :project_snippet, url: url, html: {class: "form-horizontal snippet-form"} do |f|
-if @snippet.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @snippet.errors.full_messages.each do |msg|
%li= msg
- .control-group
- = f.label :title
- .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
- .control-group
- = f.label "Lifetime"
- .controls= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'}
- .control-group
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
+ .form-group
.file-editor
- = f.label :file_name, "File"
- .controls
+ = f.label :file_name, "File", class: 'control-label'
+ .col-sm-10
.file-holder.snippet
.file-title
- = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
+ = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
@@ -31,10 +28,11 @@
= f.submit 'Create snippet', class: "btn-create btn"
- else
= f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
- - unless @snippet.new_record?
- = link_to 'Remove snippet', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}"
+ - unless @snippet.new_record?
+ .pull-right.prepend-left-20
+ = link_to 'Remove snippet', project_snippet_path(@project, @snippet), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn pull-right btn-remove delete-snippet prepend-left-10", id: "destroy_snippet_#{@snippet.id}"
+ = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
:javascript
var editor = ace.edit("editor");
diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml
index 6185e35cf6..b2c35edc44 100644
--- a/app/views/projects/snippets/_snippet.html.haml
+++ b/app/views/projects/snippets/_snippet.html.haml
@@ -5,17 +5,11 @@
%span.cgray.monospace.tiny.pull-right
= snippet.file_name
- %small.pull-right.cgray
- Expires:
- - if snippet.expires_at
- = snippet.expires_at.to_date.to_s(:short)
- - else
- Never
-
.snippet-info
= "##{snippet.id}"
%span
by
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
= snippet.author_name
- %span.light #{time_ago_in_words(snippet.created_at)} ago
+ %span.light
+ #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index c40f63d05b..e60f9a4432 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -12,4 +12,4 @@
= render partial: "projects/snippets/snippet", collection: @snippets
- if @snippets.empty?
%li
- %h3.nothing_here_message Nothing here.
+ .nothing-here-block Nothing here.
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
new file mode 100644
index 0000000000..6629e47ad3
--- /dev/null
+++ b/app/views/projects/tags/_tag.html.haml
@@ -0,0 +1,22 @@
+- commit = @repository.commit(tag.target)
+%li
+ %h4
+ = link_to project_commits_path(@project, tag.name), class: "" do
+ %i.icon-tag
+ = tag.name
+ .pull-right
+ %small.cdark
+ %i.icon-calendar
+ #{time_ago_with_tooltip(commit.committed_date)}
+ %p.prepend-left-20
+ = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace"
+ –
+ = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark"
+
+ %span.pull-right
+ - if can? current_user, :download_code, @project
+ = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
+ - if can?(current_user, :admin_project, @project)
+ = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
+ %i.icon-trash
+
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 5361517b2f..08122fb884 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -7,43 +7,18 @@
New tag
%p
- Tags give ability to mark specific points in history as being important
+ Tags give the ability to mark specific points in history as being important
%hr
- unless @tags.empty?
%ul.bordered-list
- @tags.each do |tag|
- - commit = Commit.new(Gitlab::Git::Commit.new(tag.commit))
- %li
- %h4
- = link_to project_commits_path(@project, tag.name), class: "" do
- %i.icon-tag
- = tag.name
- %small
- = truncate(tag.message || '', length: 70)
- .pull-right
- %small.cdark
- %i.icon-calendar
- = time_ago_in_words(commit.committed_date)
- ago
- %p.prepend-left-20
- = link_to commit.short_id(8), project_commit_path(@project, commit), class: "monospace"
- –
- = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "cdark"
-
- %span.pull-right
- - if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project, ref: tag.name), class: 'btn grouped btn-small' do
- %i.icon-download-alt
- Download
- - if can?(current_user, :admin_project, @project)
- = link_to project_tag_path(@project, tag.name), class: 'btn btn-small remove-row', method: :delete, confirm: 'Removed tag cannot be restored. Are you sure?', remote: true do
- %i.icon-trash
+ = render 'tag', tag: @repository.find_tag(tag)
= paginate @tags, theme: 'gitlab'
- else
- %h3.nothing_here_message
+ .nothing-here-block
Repository has no tags yet.
%br
%small
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 67030785e0..a9fd97f891 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -1,15 +1,15 @@
%h3.page-title
%i.icon-code-fork
New tag
-= form_tag project_tags_path, method: :post do
- .control-group
+= form_tag project_tags_path, method: :post, class: "form-horizontal" do
+ .form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
- .controls
- = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1
- .control-group
+ .col-sm-10
+ = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
+ .form-group
= label_tag :ref, 'Create from', class: 'control-label'
- .controls
- = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2
+ .col-sm-10
+ = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
.light Branch name or commit SHA
.form-actions
= submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml
index 5214a54e90..dd059fb99d 100644
--- a/app/views/projects/team_members/_form.html.haml
+++ b/app/views/projects/team_members/_form.html.haml
@@ -1,23 +1,23 @@
%h3.page-title
= "New project member(s)"
-= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f|
+= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project), html: { class: "form-horizontal users-project-form" } do |f|
-if @user_project_relation.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @user_project_relation.errors.full_messages.each do |msg|
%li= msg
%p 1. Choose people you want in the project
- .control-group
- = f.label :user_ids, "People"
- .controls
+ .form-group
+ = f.label :user_ids, "People", class: 'control-label'
+ .col-sm-10
= users_select_tag(:user_ids, multiple: true)
%p 2. Set access level for them
- .control-group
- = f.label :project_access, "Project Access"
- .controls= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select chosen"
+ .form-group
+ = f.label :project_access, "Project Access", class: 'control-label'
+ .col-sm-10= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select select2"
.form-actions
= f.submit 'Add users', class: "btn btn-create"
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
index 68f0800685..eceec6627b 100644
--- a/app/views/projects/team_members/_group_members.html.haml
+++ b/app/views/projects/team_members/_group_members.html.haml
@@ -1,10 +1,14 @@
+- group_users_count = @group.users_groups.count
.ui-box
.title
%strong #{@group.name}
- group members (#{@group.users_groups.count})
+ group members (#{group_users_count})
.pull-right
= link_to members_group_path(@group), class: 'btn btn-small' do
%i.icon-edit
%ul.well-list
- - @group.users_groups.order('group_access DESC').each do |member|
+ - @group.users_groups.order('group_access DESC').limit(20).each do |member|
= render 'users_groups/users_group', member: member, show_controls: false
+ - if group_users_count > 20
+ %li
+ and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)}
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
index 5c93e6e3ea..d93bb44ab9 100644
--- a/app/views/projects/team_members/_team_member.html.haml
+++ b/app/views/projects/team_members/_team_member.html.haml
@@ -7,7 +7,7 @@
= form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
- = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
+ = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
%i.icon-minus.icon-white
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%p
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml
index 1d98b98621..d3e4a76201 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/team_members/import.html.haml
@@ -3,10 +3,10 @@
%p.light
Only project members will be imported. Group members will be skipped.
%hr
-= form_tag apply_import_project_team_members_path(@project), method: 'post' do
- .padded
- = label_tag :source_project_id, "Project"
- .controls= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "chosen xxlarge", required: true)
+= form_tag apply_import_project_team_members_path(@project), method: 'post', class: 'form-horizontal' do
+ .form-group
+ = label_tag :source_project_id, "Project", class: 'control-label'
+ .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
= submit_tag 'Import project members', class: "btn btn-create"
diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml
index acbe82919f..6eccaafe3d 100644
--- a/app/views/projects/team_members/index.html.haml
+++ b/app/views/projects/team_members/index.html.haml
@@ -3,9 +3,9 @@
- if can? current_user, :admin_team_member, @project
%span.pull-right
- = link_to new_project_team_member_path(@project), class: "btn btn-new grouped", title: "New project member" do
+ = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do
New project member
- = link_to import_project_team_members_path(@project), class: "btn grouped", title: "Import members from another project" do
+ = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
%p.light
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index b179ad7d24..6fee604b55 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -1,9 +1,8 @@
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name
= tree_icon(type)
- %span= link_to truncate(blob_item.name, length: 40), project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
+ %span.str-truncated
+ = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
- %span.log_loading.hide
- Loading commit data...
- = image_tag "ajax_loader_tree.gif", width: 14
+ = render 'spinner'
%td.tree_commit{ colspan: 2 }
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 98bacb4956..0c19109445 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,13 +1,13 @@
-.file-holder#README
- .file-title
+.readme-holder#README
+ %h4.readme-file-title
%i.icon-file
= readme.name
- .file-content.wiki
+ .wiki
- if gitlab_markdown?(readme.name)
= preserve do
= markdown(readme.data)
- elsif plain_text_readme?(readme.name)
%pre.clean
= readme.data
- - else
- = raw GitHub::Markup.render(readme.name, readme.data)
+ - elsif markup?(readme.name)
+ = render_markup(readme.name, readme.data)
diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml
new file mode 100644
index 0000000000..5a9e77b63d
--- /dev/null
+++ b/app/views/projects/tree/_spinner.html.haml
@@ -0,0 +1,3 @@
+%span.log_loading.hide
+ %i.icon-spinner.icon-spin
+ Loading commit data...
diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml
index badc7d992b..ae87dbde67 100644
--- a/app/views/projects/tree/_submodule_item.html.haml
+++ b/app/views/projects/tree/_submodule_item.html.haml
@@ -1,10 +1,15 @@
+- tree, commit = submodule_links(submodule_item)
%tr{ class: "tree-item" }
%td.tree-item-file-name
= image_tag "submodule.png"
%span
- = link_to truncate(submodule_item.name, length: 40), submodule_item.submodule_url
+ = link_to truncate(submodule_item.name, length: 40), tree
@
- %span.monospace #{submodule_item.id[0..10]}
+ %span.monospace
+ - if commit.nil?
+ #{submodule_item.id[0..10]}
+ - else
+ = link_to "#{submodule_item.id[0..10]}", commit
%td
%td
%td
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index eadfd33bd3..ee850e2bc1 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -4,12 +4,16 @@
= link_to project_tree_path(@project, @ref) do
= @project.path
- tree_breadcrumbs(tree, 6) do |title, path|
- \/
%li
- if path
= link_to truncate(title, length: 40), project_tree_path(@project, path)
- else
= link_to title, '#'
+ - if current_user && @repository.branch_names.include?(@ref) && current_user.can?(:push_code, @project)
+ %li
+ = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do
+ %small
+ %i.icon-plus.light
%div#tree-content-holder.tree-content-holder
%table#tree-slider{class: "table_#{@hex_path} tree-table" }
@@ -17,15 +21,16 @@
%tr
%th Name
%th Last Update
- %th
+ %th.hidden-sm
Last Commit
-
- %i.icon-angle-right
-
- %small.light
- = link_to @commit.short_id, project_commit_path(@project, @commit)
- –
- = truncate(@commit.title, length: 50)
+ %span.last-commit
+
+ %i.icon-angle-right
+
+ %small.light
+ = link_to @commit.short_id, project_commit_path(@project, @commit)
+ –
+ = truncate(@commit.title, length: 50)
%th= link_to "history", project_commits_path(@project, @id), class: "pull-right"
- if @path.present?
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 67b5b2b96e..bd50dd4d9a 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,3 @@
-%span.tree_author= commit_author_link(commit, avatar: true, size: 16)
-= link_to_gfm truncate(commit.title, length: 80), project_commit_path(@project, commit.id), class: "tree-commit-link"
+%span.str-truncated
+ %span.tree_author= commit_author_link(commit, avatar: true, size: 16)
+ = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index f8856afc86..1b3900bcba 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -1,9 +1,8 @@
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name
= tree_icon(type)
- %span= link_to truncate(tree_item.name, length: 40), project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
+ %span.str-truncated
+ = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
%td.tree_time_ago.cgray
- %span.log_loading.hide
- Loading commit data...
- = image_tag "ajax_loader_tree.gif", width: 14
+ = render 'spinner'
%td.tree_commit{ colspan: 2 }
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 0f7692aba7..6b33493b7d 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,4 +1,6 @@
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
+- if can? current_user, :download_code, @project
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-small tree-ref-holder pull-right', split_button: true
%div#tree-holder.tree-holder
= render "tree", tree: @tree
diff --git a/app/views/projects/walls/show.html.haml b/app/views/projects/walls/show.html.haml
index 88aecee081..3e3c7c4f8d 100644
--- a/app/views/projects/walls/show.html.haml
+++ b/app/views/projects/walls/show.html.haml
@@ -3,13 +3,13 @@
- if can? current_user, :write_note, @project
.note-form-holder
- = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" } do |f|
+ = form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note wall-note-form" }, authenticity_token: true do |f|
= note_target_fields
.note_text_and_preview
= f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on'
.note-form-actions
.buttons
- = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
.note-form-option
%a.choose-btn.btn.btn-small.js-choose-note-attachment-button
@@ -17,7 +17,7 @@
%span Choose File ...
%span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hide"
+ = f.file_field :attachment, class: "js-note-attachment-input hidden"
.hint.pull-right CTRL + Enter to send message
.clearfix
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 16061c9dcb..c77ed3433d 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,39 +1,39 @@
-= form_for [@project, @wiki] do |f|
- -if @wiki.errors.any?
+= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f|
+ -if @page.errors.any?
#error_explanation
- %h2= "#{pluralize(@wiki.errors.count, "error")} prohibited this wiki from being saved:"
+ %h2= "#{pluralize(@page.errors.count, "error")} prohibited this wiki from being saved:"
%ul
- - @wiki.errors.full_messages.each do |msg|
+ - @page.errors.full_messages.each do |msg|
%li= msg
- .ui-box.ui-box-show
- .ui-box-head
- %h3.page-title
- .edit-wiki-header
- = @wiki.title.titleize
- = f.hidden_field :title, value: @wiki.title
- = f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "pull-right input-medium"
- = f.label :format, class: "pull-right", style: "padding-right: 20px;"
- .ui-box-body
- .controls
- %span.cgray
- Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
- To link to a (new) page you can just type
- %code [Link Title](page-slug)
- \.
+ = f.hidden_field :title, value: @page.title
+ .form-group
+ = f.label :format, class: 'control-label'
+ .col-sm-10
+ = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
+
+ .row
+ .col-sm-2
+ .col-sm-10
+ %p.cgray
+ Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ To link to a (new) page you can just type
+ %code [Link Title](page-slug)
+ \.
+
+ .form-group
+ = f.label :content, class: 'control-label'
+ .col-sm-10
+ = f.text_area :content, class: 'form-control js-gfm-input', rows: 18
+
+ .form-group
+ = f.label :commit_message, class: 'control-label'
+ .col-sm-10= f.text_field :message, class: 'form-control', rows: 18
- .ui-box-bottom
- .control-group
- = f.label :content
- .controls= f.text_area :content, class: 'span8 js-gfm-input'
- .ui-box-bottom
- .control-group
- = f.label :commit_message
- .controls= f.text_field :message, class: 'span8'
.form-actions
- - if @wiki && @wiki.persisted?
+ - if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", project_wiki_path(@project, @wiki), class: "btn btn-cancel"
+ = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel"
- else
= f.submit 'Create page', class: "btn-create btn"
= link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 1001bb10c4..68d7087323 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,8 +1,8 @@
%span.pull-right
- - if (@wiki && @wiki.persisted?)
- = link_to history_project_wiki_path(@project, @wiki), class: "btn grouped" do
+ - if (@page && @page.persisted?)
+ = link_to history_project_wiki_path(@project, @page), class: "btn btn-grouped" do
Page History
- if can?(current_user, :write_wiki, @project)
- = link_to edit_project_wiki_path(@project, @wiki), class: "btn grouped" do
+ = link_to edit_project_wiki_path(@project, @page), class: "btn btn-grouped" do
%i.icon-edit
Edit
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index f64772b200..1ce292a02d 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -1,12 +1,14 @@
%div#modal-new-wiki.modal.hide
- .modal-header
- %a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title New Wiki Page
- .modal-body
- = label_tag :new_wiki_path do
- %span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'input-xlarge', required: true, :'data-wikis-path' => project_wikis_path(@project)
- %p.hint
- Please don't use spaces and slashes
- .modal-footer
- = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title New Wiki Page
+ .modal-body
+ = label_tag :new_wiki_path do
+ %span Page slug
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project)
+ %p.hint
+ Please don't use spaces.
+ .modal-footer
+ = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 66be82777c..5347caf000 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,10 +1,13 @@
= render 'nav'
-%h3.page-title
- Editing page
+.pull-right
= render 'main_links'
+%h3.page-title
+ Editing -
+ %span.light #{@page.title}
+%hr
= render 'form'
.pull-right
- - if @wiki.persisted? && 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, class: "btn btn-small btn-remove" do
+ - if @page.persisted? && can?(current_user, :admin_wiki, @project)
+ = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do
Delete this page
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 692eda4e53..365edb524f 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,13 +1,12 @@
= render 'nav'
-%h3.page-title
- Git access for
- %strong= @gollum_wiki.path_with_namespace
+.row
+ .col-sm-6
+ %h3.page-title
+ Git access for
+ %strong= @project_wiki.path_with_namespace
- .form-horizontal.pull-right
- .git-clone-holder
- %button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH
- %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= gitlab_config.protocol.upcase
- = text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true
+ .col-sm-6
+ = render "shared/clone_panel", project: @project_wiki
.git-empty
%fieldset
@@ -19,8 +18,8 @@
%legend Clone Your Wiki:
%pre.dark
:preserve
- git clone #{@gollum_wiki.ssh_url_to_repo}
- cd #{@gollum_wiki.path}
+ git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
+ cd #{@project_wiki.path}
%legend Start Gollum And Edit Locally:
%pre.dark
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 344a5e88e6..7bc566cf7f 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,9 +1,9 @@
= render 'nav'
%h3.page-title
%span.light History for
- = link_to @wiki.title.titleize, project_wiki_path(@project, @wiki)
+ = link_to @page.title, project_wiki_path(@project, @page)
-%table
+%table.table
%thead
%tr
%th Page version
@@ -12,19 +12,18 @@
%th Last updated
%th Format
%tbody
- - @wiki.versions.each do |version|
+ - @page.versions.each do |version|
- commit = version
%tr
%td
- = link_to project_wiki_path(@project, @wiki, version_id: commit.id) do
+ = link_to project_wiki_path(@project, @page, version_id: commit.id) do
= commit.short_id
%td
= commit_author_link(commit, avatar: true, size: 24)
%td
= commit.title
%td
- = time_ago_in_words(version.date)
- ago
+ #{time_ago_with_tooltip(version.date)}
%td
%strong
- = @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format)
+ = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 59d104a985..74317faf9d 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,7 +5,8 @@
- @wiki_pages.each do |wiki_page|
%li
%h4
- = link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page)
+ = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
- %small Last edited #{time_ago_in_words(wiki_page.commit.created_at)} ago
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.created_at)}
+= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 63e7f12b7f..cb923e4ca3 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,15 +1,20 @@
= render 'nav'
%h3.page-title
- = @wiki.title.titleize
+ = @page.title
= render 'main_links'
-- if @wiki.historical?
+- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}.
+ You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", history_project_wiki_path(@project, @page)}.
-.file-holder
- .file-content.wiki
+%hr
+
+.wiki-holder
+ .wiki
= preserve do
- = render_wiki_content(@wiki)
+ = render_wiki_content(@page)
-%p.time Last edited by #{commit_author_link(@wiki.commit, avatar: true, size: 16)} #{time_ago_in_words @wiki.commit.created_at} ago
+%hr
+
+.wiki-last-edit-by
+ Last edited by #{commit_author_link(@page.commit, avatar: true, size: 16)} #{time_ago_with_tooltip(@page.commit.created_at)}
diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml
index 21aee64457..624ec0b9b9 100644
--- a/app/views/public/projects/index.html.haml
+++ b/app/views/public/projects/index.html.haml
@@ -1,16 +1,38 @@
-.row
- .span6
- %h3.page-title
- Projects (#{@projects.total_count})
- .light
- You can browse public projects in read-only mode until signed in.
+%h3.page-title
+ Projects (#{@projects.total_count})
+.light
+ You can browse public projects in read-only mode until signed in.
+%hr
+.clearfix
+ .pull-left
+ = form_tag public_projects_path, method: :get, class: 'form-inline form-tiny' do |f|
+ .form-group
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
+ .form-group
+ = submit_tag 'Search', class: "btn btn-primary wide"
+
+ .pull-right
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to public_projects_path(sort: nil) do
+ Name
+ = link_to public_projects_path(sort: 'newest') do
+ Newest
+ = link_to public_projects_path(sort: 'oldest') do
+ Oldest
+ = link_to public_projects_path(sort: 'recently_updated') do
+ Recently updated
+ = link_to public_projects_path(sort: 'last_updated') do
+ Last updated
- .span6
- .pull-right
- = form_tag public_projects_path, method: :get, class: 'form-inline' do |f|
- .search-holder
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "span3 search-text-input", id: "projects_search"
- = submit_tag 'Search', class: "btn btn-primary wide"
%hr
.public-projects
%ul.bordered-list.top-list
@@ -19,6 +41,10 @@
%h4
= link_to project_path(project) do
= project.name_with_namespace
+ - if project.internal?
+ %small.access-icon
+ = internal_icon
+ Internal
.pull-right
%pre.public-clone git clone #{project.http_url_to_repo}
@@ -37,6 +63,6 @@
%i.icon-warning-sign
Empty repository
- unless @projects.present?
- %h3.nothing_here_message No public projects
+ .nothing-here-block No public projects
= paginate @projects, theme: "gitlab"
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index f7a00b2348..979b18e385 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -9,7 +9,7 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to search_path(group_id: nil) do
+ = link_to search_path(group_id: nil, search: params[:search]) do
Any
- current_user.authorized_groups.sort_by(&:name).each do |group|
%li
@@ -27,7 +27,7 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to search_path(project_id: nil) do
+ = link_to search_path(project_id: nil, search: params[:search]) do
Any
- current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
%li
diff --git a/app/views/search/_global_results.html.haml b/app/views/search/_global_results.html.haml
new file mode 100644
index 0000000000..7f4f0e5e00
--- /dev/null
+++ b/app/views/search/_global_results.html.haml
@@ -0,0 +1,5 @@
+.search_results
+ %ul.bordered-list
+ = render partial: "search/results/project", collection: @search_results[:projects]
+ = render partial: "search/results/merge_request", collection: @search_results[:merge_requests]
+ = render partial: "search/results/issue", collection: @search_results[:issues]
diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml
new file mode 100644
index 0000000000..f285bda573
--- /dev/null
+++ b/app/views/search/_project_results.html.haml
@@ -0,0 +1,17 @@
+%ul.nav.nav-tabs
+ %li{class: ("active" if params[:search_code].present?)}
+ = link_to search_path(params.merge(search_code: true)) do
+ Repository Code
+ %li{class: ("active" if params[:search_code].blank?)}
+ = link_to search_path(params.merge(search_code: nil)) do
+ Issues and Merge requests
+
+.search_results
+ - if params[:search_code].present?
+ .blob-results
+ = render partial: "search/results/blob", collection: @search_results[:blobs]
+ = paginate @search_results[:blobs], theme: 'gitlab'
+ - else
+ %ul.bordered-list
+ = render partial: "search/results/merge_request", collection: @search_results[:merge_requests]
+ = render partial: "search/results/issue", collection: @search_results[:issues]
diff --git a/app/views/search/_result.html.haml b/app/views/search/_result.html.haml
deleted file mode 100644
index a55ac1a759..0000000000
--- a/app/views/search/_result.html.haml
+++ /dev/null
@@ -1,73 +0,0 @@
-%fieldset
- %legend
- Search results
- %span.cgray (#{@total_results})
-
-- if @project
- %ul.nav.nav-pills
- %li{class: ("active" if params[:search_code].present?)}
- = link_to search_path(params.merge(search_code: true)) do
- Repository Code
- %li{class: ("active" if params[:search_code].blank?)}
- = link_to search_path(params.merge(search_code: nil)) do
- Everything else
-
-.search_results
- %ul.bordered-list
- - @projects.each do |project|
- %li
- project:
- = link_to project do
- %strong.term= project.name_with_namespace
- - @merge_requests.each do |merge_request|
- %li
- merge request:
- = link_to [merge_request.target_project, merge_request] do
- %span ##{merge_request.iid}
- %strong.term
- = truncate merge_request.title, length: 50
- - if merge_request.for_fork?
- %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
- - else
- %span.light (#{merge_request.source_branch} → #{merge_request.target_branch})
- - if merge_request.closed?
- %span.label Closed
-
- - @issues.each do |issue|
- %li
- issue:
- = link_to [issue.project, issue] do
- %span ##{issue.iid}
- %strong.term
- = truncate issue.title, length: 50
- %span.light (#{issue.project.name_with_namespace})
- - if issue.closed?
- %span.label Closed
-
- - @notes.each do |note|
- %li
- note on issue:
- = link_to [note.project, note.noteable] do
- %span ##{note.noteable.iid}
- %strong.term
- = truncate note.noteable.title, length: 50
- %span.light (#{note.project.name_with_namespace})
- - if note.noteable.closed?
- %span.label Closed
-
- - @wiki_pages.each do |wiki_page|
- %li
- wiki:
- = link_to project_wiki_path(wiki_page.project, wiki_page) do
- %strong.term
- = truncate wiki_page.title, length: 50
- %span.light (#{wiki_page.project.name_with_namespace})
-
- - @blobs.each do |blob|
- = render 'blob', blob: blob
-
- = paginate @blobs, theme: 'gitlab'
-
-:javascript
- $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
-
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
new file mode 100644
index 0000000000..2336d0f71d
--- /dev/null
+++ b/app/views/search/_results.html.haml
@@ -0,0 +1,17 @@
+%h4
+ #{@search_results[:total_results]} results found
+ - if @project
+ for #{link_to @project.name_with_namespace, @project}
+ - elsif @group
+ for #{link_to @group.name, @group}
+
+%hr
+
+- if @project
+ = render "project_results"
+- else
+ = render "global_results"
+
+:javascript
+ $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
+
diff --git a/app/views/search/_blob.html.haml b/app/views/search/results/_blob.html.haml
similarity index 62%
rename from app/views/search/_blob.html.haml
rename to app/views/search/results/_blob.html.haml
index 559fdd794f..f9d217e840 100644
--- a/app/views/search/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,4 @@
-%li
+.blob-result
.file-holder
.file-title
= link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
@@ -6,5 +6,4 @@
%strong
= blob.filename
.file-content.code.term
- %div{class: user_color_scheme_class}
- = raw blob.colorize( formatter: :gitlab, options: { first_line_number: blob.startline } )
+ = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
new file mode 100644
index 0000000000..7a24b76bce
--- /dev/null
+++ b/app/views/search/results/_issue.html.haml
@@ -0,0 +1,9 @@
+%li
+ issue:
+ = link_to [issue.project, issue] do
+ %span ##{issue.iid}
+ %strong.term
+ = truncate issue.title, length: 50
+ %span.light (#{issue.project.name_with_namespace})
+ - if issue.closed?
+ %span.label Closed
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
new file mode 100644
index 0000000000..22d7587f6c
--- /dev/null
+++ b/app/views/search/results/_merge_request.html.haml
@@ -0,0 +1,12 @@
+%li
+ merge request:
+ = link_to [merge_request.target_project, merge_request] do
+ %span ##{merge_request.iid}
+ %strong.term
+ = truncate merge_request.title, length: 50
+ - if merge_request.for_fork?
+ %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
+ - else
+ %span.light (#{merge_request.source_branch} → #{merge_request.target_branch})
+ - if merge_request.closed?
+ %span.label Closed
diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml
new file mode 100644
index 0000000000..abc86c72be
--- /dev/null
+++ b/app/views/search/results/_project.html.haml
@@ -0,0 +1,7 @@
+%li
+ project:
+ = link_to project do
+ %strong.term= project.name_with_namespace
+ - if project.description.present?
+ –
+ %span.light.term= project.description
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index f1f65981b3..3b6f10d4d9 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,16 +1,20 @@
-= form_tag search_path, method: :get, class: 'form-inline' do |f|
- .search-holder
- = label_tag :search do
- %span Looking for
- .controls
- = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search"
+= form_tag search_path, method: :get, class: 'form-horizontal' do |f|
+ .search-holder.clearfix
+ .form-group
+ = label_tag :search, class: 'control-label' do
+ %span Looking for
+ .col-sm-6
+ = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
+ .col-sm-4
+ = submit_tag 'Search', class: "btn btn-create"
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = render 'filter', f: f
= hidden_field_tag :project_id, params[:project_id]
= hidden_field_tag :group_id, params[:group_id]
= hidden_field_tag :search_code, params[:search_code]
- = submit_tag 'Search', class: "btn btn-create"
- .prepend-top-10
- = render 'filter', f: f
.results.prepend-top-10
- if params[:search].present?
- = render 'search/result'
+ = render 'search/results'
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index ac7b9ee7f2..8cd426c71e 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,4 +1,6 @@
-.git-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.protocol.upcase
- = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5", readonly: true
+- project = project || @project
+.git-clone-holder.input-group
+ .input-group-btn
+ %button{class: "btn #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => project.ssh_url_to_repo} SSH
+ %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => project.http_url_to_repo}= gitlab_config.protocol.upcase
+ = text_field_tag :project_clone, default_url_to_repo(project), class: "one_click_select form-control", readonly: true
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
new file mode 100644
index 0000000000..cca7a0efc9
--- /dev/null
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -0,0 +1,5 @@
+.commit-message-container
+ .max-width-marker
+ -# When the `ch` CSS length unit becomes widely supported `http://www.quirksmode.org/css/units-values` remove this workaround.
+ = 'a' * 72
+ = textarea
diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml
new file mode 100644
index 0000000000..338236c075
--- /dev/null
+++ b/app/views/shared/_file_hljs.html.haml
@@ -0,0 +1,12 @@
+%div.highlighted-data{class: user_color_scheme_class}
+ .line-numbers
+ - blob.data.lines.to_a.size.times do |index|
+ - offset = defined?(first_line_number) ? first_line_number : 1
+ - i = index + offset
+ = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ %i.icon-link
+ = i
+ .highlight
+ %pre
+ %code
+ = blob.data
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
index 2f051cea48..0becf531cc 100644
--- a/app/views/shared/_filter.html.haml
+++ b/app/views/shared/_filter.html.haml
@@ -1,29 +1,44 @@
-= form_tag filter_path(entity), method: 'get' do
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:status].blank?)}
- = link_to filter_path(entity, status: nil) do
- Open
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to filter_path(entity, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to filter_path(entity, status: 'all') do
- All
+.side-filters.hidden-xs.hidden-sm
+ = form_tag filter_path(entity), method: 'get' do
+ - if current_user
+ %fieldset.scope-filter
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if params[:scope] == 'assigned-to-me')}
+ = link_to filter_path(entity, scope: 'assigned-to-me') do
+ Assigned to me
+ %li{class: ("active" if params[:scope] == 'authored')}
+ = link_to filter_path(entity, scope: 'authored') do
+ Created by me
+ %li{class: ("active" if params[:scope] == 'all')}
+ = link_to filter_path(entity, scope: 'all') do
+ Everyone's
- %fieldset
- %legend Projects
- %ul.nav.nav-pills.nav-pills-small.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 filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
+ %fieldset.status-filter
+ %legend State
+ %ul.nav.nav-pills
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to filter_path(entity, state: 'opened') do
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to filter_path(entity, state: 'closed') do
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to filter_path(entity, state: 'all') do
+ All
- %fieldset
- - if params[:status].present? || params[:project_id].present?
- = link_to filter_path(entity, status: nil, project_id: nil), class: 'pull-right cgray' do
- %i.icon-remove
- %strong Clear filter
+ %fieldset
+ %legend Projects
+ %ul.nav.nav-pills.nav-stacked.nav-small
+ - @projects.each do |project|
+ - unless entities_per_project(project, entity).zero?
+ %li{class: ("active" if params[:project_id] == project.id.to_s)}
+ = link_to filter_path(entity, project_id: project.id) do
+ = project.name_with_namespace
+ %small.pull-right= entities_per_project(project, entity)
+
+ %fieldset
+ - if params[:state].present? || params[:project_id].present?
+ = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
+ %i.icon-remove
+ %strong Clear filter
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 3b3888a50e..087b6632e8 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,6 +1,6 @@
- if @issues.any?
- @issues.group_by(&:project).each do |group|
- .ui-box.small-box
+ .ui-box.ui-box-small
- project = group[0]
.title
= link_to_project project
@@ -11,5 +11,5 @@
= render 'projects/issues/issue', issue: issue
= paginate @issues, theme: "gitlab"
- else
- %p.nothing_here_message No issues to show
+ .nothing-here-block No issues to show
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index b7a7ca8fcc..f40b7be486 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,13 +1,14 @@
- if @merge_requests.any?
- @merge_requests.group_by(&:target_project).each do |group|
- .ui-box.small-box
+ .ui-box.ui-box-small
- project = group[0]
.title
= link_to_project project
+ = link_to 'show all', project_merge_requests_path(project), class: 'pull-right'
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab"
- else
- %h3.nothing_here_message No merge requests to show
+ .nothing-here-block No merge requests to show
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index 6d363807d6..e70eb4d01b 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,3 +1,14 @@
-- if current_user.require_ssh_key? && alert.blank? && notice.blank?
- %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_profile_key_path} to your profile
+- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key
+ .no-ssh-key-message
+ .container
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
+ .pull-right.hidden-xs
+ = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
+ .links-xs.visible-xs
+ = link_to "Add key", new_profile_key_path
+ |
+ = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
+ |
+ = link_to 'Later', '#', class: 'hide-no-ssh-message'
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
index f3d032ef98..7936a038be 100644
--- a/app/views/shared/_project_filter.html.haml
+++ b/app/views/shared/_project_filter.html.haml
@@ -1,32 +1,55 @@
-= form_tag project_entities_path, method: 'get' do
- %fieldset
+.side-filters.hidden-xs.hidden-sm
+ = form_tag project_entities_path, method: 'get' do
- if current_user
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope].blank?)}
- = link_to project_filter_path(scope: nil) do
- Everyone's
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to project_filter_path(scope: 'assigned-to-me') do
- Assigned to me
- %li{class: ("active" if params[:scope] == 'created-by-me')}
- = link_to project_filter_path(scope: 'created-by-me') do
- Created by me
+ %fieldset
+ %ul.nav.nav-pills.nav-stacked
+ %li{class: ("active" if params[:scope] == 'all')}
+ = link_to project_filter_path(scope: 'all') do
+ Everyone's
+ %li{class: ("active" if params[:scope] == 'assigned-to-me')}
+ = link_to project_filter_path(scope: 'assigned-to-me') do
+ Assigned to me
+ %li{class: ("active" if params[:scope] == 'created-by-me')}
+ = link_to project_filter_path(scope: 'created-by-me') do
+ Created by me
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:state].blank?)}
- = link_to project_filter_path(state: nil) do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to project_filter_path(state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to project_filter_path(state: 'all') do
- All
+ %fieldset
+ %legend State
+ %ul.nav.nav-pills
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to project_filter_path(state: 'opened') do
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to project_filter_path(state: 'closed') do
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to project_filter_path(state: 'all') do
+ All
- %fieldset
- - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
- = link_to project_entities_path, class: 'cgray pull-right' do
- %i.icon-remove
- %strong Clear filter
+ - if defined?(labels)
+ %fieldset
+ %legend Labels
+ %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
+ - issue_label_names.each do |label_name|
+ %li{class: label_filter_class(label_name)}
+ = link_to labels_filter_path(label_name) do
+ %span{class: "label #{label_css_class(label_name)}"}
+ %i.icon-tag
+ = label_name
+ - if selected_label?(label_name)
+ .pull-right
+ %i.icon-remove
+
+ - if issue_label_names.empty?
+ .light-well
+ Add first label to your issues
+ %br
+ or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels
+
+ %fieldset
+ - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
+ = link_to project_entities_path, class: 'cgray pull-right' do
+ %i.icon-remove
+ %strong Clear filter
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index c97f8ba0f0..7dec48e658 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,4 +1,4 @@
.gitlab-promo
- = link_to "Homepage", "http://gitlab.org"
- = link_to "Blog", "http://blog.gitlab.org"
+ = link_to "Homepage", "https://www.gitlab.com/"
+ = link_to "Blog", "https://www.gitlab.com/blog/"
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index dc8c656e12..4d9534f49b 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,5 +1,5 @@
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
- = select_tag "ref", grouped_options_refs, class: "project-refs-select chosen"
+ = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
new file mode 100644
index 0000000000..7b37b39780
--- /dev/null
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -0,0 +1,22 @@
+.dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = @sort
+ - else
+ Newest
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(sort: 'newest') do
+ Newest
+ = link_to project_filter_path(sort: 'oldest') do
+ Oldest
+ = link_to project_filter_path(sort: 'recently_updated') do
+ Recently updated
+ = link_to project_filter_path(sort: 'last_updated') do
+ Last updated
+ = link_to project_filter_path(sort: 'milestone_due_soon') do
+ Milestone due soon
+ = link_to project_filter_path(sort: 'milestone_due_later') do
+ Milestone due later
diff --git a/app/views/snippets/_blob.html.haml b/app/views/snippets/_blob.html.haml
index c2e0d97a11..15867f071e 100644
--- a/app/views/snippets/_blob.html.haml
+++ b/app/views/snippets/_blob.html.haml
@@ -6,11 +6,6 @@
.btn-group.tree-btn-group.pull-right
- if @snippet.author == current_user
= link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-tiny", title: 'Edit Snippet'
- = link_to "Delete", snippet_path(@snippet), method: :delete, confirm: "Are you sure?", class: "btn btn-tiny", title: 'Delete Snippet'
+ = link_to "Delete", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-tiny", title: 'Delete Snippet'
= link_to "Raw", raw_snippet_path(@snippet), class: "btn btn-tiny", 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
+ = render 'snippets/blob_content'
diff --git a/app/views/snippets/_blob_content.html.haml b/app/views/snippets/_blob_content.html.haml
new file mode 100644
index 0000000000..8cec6168ab
--- /dev/null
+++ b/app/views/snippets/_blob_content.html.haml
@@ -0,0 +1,14 @@
+- unless @snippet.content.empty?
+ - if gitlab_markdown?(@snippet.file_name)
+ .file-content.wiki
+ = preserve do
+ = markdown(@snippet.data)
+ - elsif markup?(@snippet.file_name)
+ .file-content.wiki
+ = render_markup(@snippet.file_name, @snippet.data)
+ - else
+ .file-content.code
+ = render 'shared/file_hljs', blob: @snippet
+- else
+ .file-content.code
+ .nothing-here-block Empty file
diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml
index e77550e7be..d466dc1af1 100644
--- a/app/views/snippets/_form.html.haml
+++ b/app/views/snippets/_form.html.haml
@@ -2,27 +2,38 @@
= @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}"
%hr
.snippet-form-holder
- = form_for @snippet, as: :personal_snippet, url: url do |f|
+ = form_for @snippet, as: :personal_snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f|
-if @snippet.errors.any?
- .alert.alert-error
+ .alert.alert-danger
%ul
- @snippet.errors.full_messages.each do |msg|
%li= msg
- .control-group
- = f.label :title
- .controls= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true
- .control-group
- = f.label "Private?"
- .controls
- = f.check_box :private, {class: ''}
- .control-group
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
+ .form-group
+ = f.label "Access", class: 'control-label'
+ .col-sm-10
+ = f.label :private_true, class: 'radio-label' do
+ = f.radio_button :private, true
+ %span
+ %strong Private
+ (only you can see this snippet)
+ %br
+ = f.label :private_false, class: 'radio-label' do
+ = f.radio_button :private, false
+ %span
+ %strong Public
+ (GitLab users can see this snippet)
+
+ .form-group
.file-editor
- = f.label :file_name, "File"
- .controls
+ = f.label :file_name, "File", class: 'control-label'
+ .col-sm-10
.file-holder.snippet
.file-title
- = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true
+ = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
@@ -33,9 +44,10 @@
- else
= f.submit 'Save', class: "btn-save btn"
- = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
- unless @snippet.new_record?
- .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}"
+ .pull-right.prepend-left-20
+ = link_to 'Remove', snippet_path(@snippet), data: { confirm: 'Removed snippet cannot be restored! Are you sure?'}, method: :delete, class: "btn btn-remove delete-snippet", id: "destroy_snippet_#{@snippet.id}"
+ = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
:javascript
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
index 8514bc3ddd..e6f8316733 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -3,7 +3,7 @@
= link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60)
- if snippet.private?
- %span.label.label-success
+ %span.label.label-gray
%i.icon-lock
private
%span.cgray.monospace.tiny.pull-right
@@ -20,4 +20,4 @@
= link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name
- %span.light #{time_ago_in_words(snippet.created_at)} ago
+ %span.light #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 05365dd8b8..40df42b6cf 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -2,6 +2,6 @@
= render partial: 'snippet', collection: @snippets
- if @snippets.empty?
%li
- %h3.nothing_here_message Nothing here.
+ .nothing-here-block Nothing here.
= paginate @snippets, theme: 'gitlab'
diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml
index 51030f965a..90ddc7198f 100644
--- a/app/views/snippets/current_user_index.html.haml
+++ b/app/views/snippets/current_user_index.html.haml
@@ -1,9 +1,9 @@
%h3.page-title
My Snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
- = link_to snippets_path, class: "btn grouped" do
+ = link_to snippets_path, class: "btn btn-grouped" do
Discover snippets
%p.light
@@ -11,7 +11,7 @@
%hr
.row
- .span3
+ .col-md-3
%ul.nav.nav-pills.nav-stacked
= nav_tab :scope, nil do
= link_to user_snippets_path(@user) do
@@ -29,6 +29,6 @@
%span.pull-right
= @user.snippets.public.count
- .span9.my-snippets
+ .col-md-9.my-snippets
= render 'snippets'
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 2f6c914a15..cea2517a8e 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -2,9 +2,9 @@
Public snippets
.pull-right
- = link_to new_snippet_path, class: "btn btn-new grouped", title: "New Snippet" do
+ = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
- = link_to user_snippets_path(current_user), class: "btn grouped" do
+ = link_to user_snippets_path(current_user), class: "btn btn-grouped" do
My snippets
%p.light
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
new file mode 100644
index 0000000000..412df943fc
--- /dev/null
+++ b/app/views/users/_groups.html.haml
@@ -0,0 +1,3 @@
+- groups.each do |group|
+ = link_to group, class: 'profile-groups-avatars', :title => group.name do
+ = image_tag group_icon(group.path)
\ No newline at end of file
diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml
index 4cd1eebdf9..7ffd43e837 100644
--- a/app/views/users/_profile.html.haml
+++ b/app/views/users/_profile.html.haml
@@ -8,7 +8,7 @@
- unless user.skype.blank?
%li
%span.light Skype:
- %strong= user.skype
+ %strong= link_to user.skype, "skype:#{user.skype}"
- unless user.linkedin.blank?
%li
%span.light LinkedIn:
@@ -16,7 +16,11 @@
- unless user.twitter.blank?
%li
%span.light Twitter:
- %strong= user.twitter
+ %strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}"
+ - unless user.website_url.blank?
+ %li
+ %span.light Website:
+ %strong= link_to user.short_website_url, user.full_website_url
- unless user.bio.blank?
%li
%span.light Bio:
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 53a0a9232a..edcaf3acf9 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,5 +1,5 @@
.row
- .span8
+ .col-md-8
%h3.page-title
= image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= @user.name
@@ -9,13 +9,15 @@
%i.icon-edit
Edit Profile settings
%br
- %small #{@user.username}
+ %span.user-show-username #{@user.username}
%br
%small member since #{@user.created_at.stamp("Nov 12, 2031")}
.clearfix
+ %h4 Groups:
+ = render 'groups', groups: @groups
%hr
%h4 User Activity:
= render @events
- .span4
+ .col-md-4
= render 'profile', user: @user
- = render 'projects', user: @user
+ = render 'projects'
diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml
index 5934ff72e8..ad363eaba2 100644
--- a/app/views/users_groups/_users_group.html.haml
+++ b/app/views/users_groups/_users_group.html.haml
@@ -1,23 +1,31 @@
- user = member.user
- return unless user
+- show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
- = image_tag avatar_icon(user.email, 16), class: "avatar s16"
- %strong= user.name
- %span.cgray= user.username
- - if user == current_user
- %span.label.label-success It's you
+ %span{class: ("list-item-name" if show_controls)}
+ = image_tag avatar_icon(user.email, 16), class: "avatar s16"
+ %strong= user.name
+ %span.cgray= user.username
+ - if user == current_user
+ %span.label.label-success It's you
- %span.pull-right
- %strong= member.human_access
+ - if show_roles
+ %span.pull-right
+ %strong= member.human_access
+ - if show_controls
+ - if can?(current_user, :modify, member)
+ = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
+ %i.icon-edit
+ - if can?(current_user, :destroy, member)
+ - if current_user == member.user
+ = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.icon-minus.icon-white
+ - else
+ = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %i.icon-minus.icon-white
- - if show_controls && can?(current_user, :manage_group, @group) && current_user != user
- = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
- %i.icon-edit
- = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
- %i.icon-minus.icon-white
-
- .edit-member.hide.js-toggle-content
- = form_for [@group, member], remote: true do |f|
- .alert.prepend-top-20
- = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
- = f.submit 'Save', class: 'btn btn-save btn-small'
+ .edit-member.hide.js-toggle-content
+ = form_for [@group, member], remote: true do |f|
+ .alert.prepend-top-20
+ = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access)
+ = f.submit 'Save', class: 'btn btn-save btn-small'
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index bded53b2f2..788d9065a7 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,6 +1,6 @@
.votes.votes-block
.progress
- .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
- .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
+ .progress-bar.progress-bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
+ .progress-bar.progress-bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
.upvotes= "#{votable.upvotes} up"
.downvotes= "#{votable.downvotes} down"
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
new file mode 100644
index 0000000000..5e81810cbd
--- /dev/null
+++ b/app/workers/emails_on_push_worker.rb
@@ -0,0 +1,25 @@
+class EmailsOnPushWorker
+ include Sidekiq::Worker
+
+ def perform(project_id, recipients, push_data)
+ project = Project.find(project_id)
+ before_sha = push_data["before"]
+ after_sha = push_data["after"]
+ branch = push_data["ref"]
+ author_id = push_data["user_id"]
+
+ if before_sha =~ /^000000/ || after_sha =~ /^000000/
+ # skip if new branch was pushed or branch was removed
+ return true
+ end
+
+ compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha, MergeRequestDiff::COMMITS_SAFE_SIZE)
+
+ # Do not send emails if git compare failed
+ return false unless compare && compare.commits.present?
+
+ recipients.split(" ").each do |recipient|
+ Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver
+ end
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 6416aa608e..3b0cf77d42 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -29,10 +29,20 @@ class PostReceive
return false
end
- GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ if tag?(ref)
+ GitTagPushService.new.execute(project, user, oldrev, newrev, ref)
+ else
+ GitPushService.new.execute(project, user, oldrev, newrev, ref)
+ end
end
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
+
+ private
+
+ def tag?(ref)
+ !!(/refs\/tags\/(.*)/.match(ref))
+ end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index a3b4bd0c9b..e66df8c4db 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -11,12 +11,11 @@ class RepositoryImportWorker
project.import_url)
if result
- project.imported = true
+ project.import_finish
project.save
project.satellite.create unless project.satellite.exists?
- project.discover_default_branch
else
- project.imported = false
+ project.import_fail
end
end
end
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 0000000000..66e9889e8b
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/pkgr_before_precompile.sh b/bin/pkgr_before_precompile.sh
new file mode 100755
index 0000000000..283abb6a0c
--- /dev/null
+++ b/bin/pkgr_before_precompile.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+
+for file in config/*.yml.example; do
+ cp ${file} config/$(basename ${file} .example)
+done
+
+# Allow to override the Gitlab URL from an environment variable, as this will avoid having to change the configuration file for simple deployments.
+config=$(echo '<% gitlab_url = URI(ENV["GITLAB_URL"] || "http://localhost:80") %>' | cat - config/gitlab.yml)
+echo "$config" > config/gitlab.yml
+sed -i "s/host: localhost/host: <%= gitlab_url.host %>/" config/gitlab.yml
+sed -i "s/port: 80/port: <%= gitlab_url.port %>/" config/gitlab.yml
+sed -i "s/https: false/https: <%= gitlab_url.scheme == 'https' %>/" config/gitlab.yml
+
+# No need for config file. Will be taken care of by REDIS_URL env variable
+rm config/resque.yml
+
+# Set default unicorn.rb file
+echo "" > config/unicorn.rb
+
+# Required for assets precompilation
+sudo service postgresql start
diff --git a/bin/rails b/bin/rails
new file mode 100755
index 0000000000..7feb6a30e6
--- /dev/null
+++ b/bin/rails
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+APP_PATH = File.expand_path('../../config/application', __FILE__)
+require_relative '../config/boot'
+require 'rails/commands'
diff --git a/bin/rake b/bin/rake
new file mode 100755
index 0000000000..8017a0271d
--- /dev/null
+++ b/bin/rake
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/rspec b/bin/rspec
new file mode 100755
index 0000000000..41e37089ac
--- /dev/null
+++ b/bin/rspec
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require 'bundler/setup'
+load Gem.bin_path('rspec', 'rspec')
diff --git a/bin/spinach b/bin/spinach
new file mode 100755
index 0000000000..a080e286cf
--- /dev/null
+++ b/bin/spinach
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path("../spring", __FILE__)
+rescue LoadError
+end
+require 'bundler/setup'
+load Gem.bin_path('spinach', 'spinach')
diff --git a/bin/spring b/bin/spring
new file mode 100755
index 0000000000..253ec37c34
--- /dev/null
+++ b/bin/spring
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast
+# It gets overwritten when you run the `spring binstub` command
+
+unless defined?(Spring)
+ require "rubygems"
+ require "bundler"
+
+ if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
+ ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
+ ENV["GEM_HOME"] = ""
+ Gem.paths = ENV
+
+ gem "spring", match[1]
+ require "spring/binstub"
+ end
+end
diff --git a/config.ru b/config.ru
index dfd2d86223..e90863a5c2 100644
--- a/config.ru
+++ b/config.ru
@@ -1,5 +1,14 @@
# This file is used by Rack-based servers to start the application.
+if defined?(Unicorn)
+ require 'unicorn'
+ # Unicorn self-process killer
+ require 'unicorn/worker_killer'
+
+ # Max memory size (RSS) per worker
+ use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20))
+end
+
require ::File.expand_path('../config/environment', __FILE__)
map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
diff --git a/config/application.rb b/config/application.rb
index d85bcab788..f087d3507b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,13 +1,9 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
+require 'devise'
-if defined?(Bundler)
- # If you precompile assets before deploying to production, use this line
- # Bundler.require(*Rails.groups(assets: %w(development test)))
- # If you want your assets lazily compiled in production, use this line
- Bundler.require(:default, :assets, Rails.env)
-end
+Bundler.require(:default, Rails.env)
module Gitlab
class Application < Rails::Application
@@ -16,18 +12,15 @@ module Gitlab
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns)
+ config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
- config.active_record.observers = :activity_observer,
+ config.active_record.observers = :milestone_observer,
:project_activity_cache_observer,
- :issue_observer,
- :key_observer,
- :merge_request_observer,
:note_observer,
:project_observer,
:system_hook_observer,
@@ -42,6 +35,7 @@ module Gitlab
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
+ config.i18n.enforce_available_locales = false
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
@@ -65,20 +59,35 @@ module Gitlab
# Enable the asset pipeline
config.assets.enabled = true
+ config.assets.paths << Emoji.images_path
+ config.assets.precompile << "emoji/*.png"
+ config.assets.precompile << "print.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+ # Relative url support
# Uncomment and customize the last line to run in a non-root path
- # WARNING: This feature is known to work, but unsupported
- # Note that three settings need to be changed for this to work.
+ # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
+ # Note that following settings need to be changed for this to work.
# 1) In your application.rb file: config.relative_url_root = "/gitlab"
# 2) In your gitlab.yml file: relative_url_root: /gitlab
# 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
+ # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"
+ # 5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with "location ~ ^/(assets)/"
+ #
+ # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
#
# config.relative_url_root = "/gitlab"
- # Uncomment to enable rack attack middleware
- # config.middleware.use Rack::Attack
+ config.middleware.use Rack::Attack
+
+ # Allow access to GitLab API from other domains
+ config.middleware.use Rack::Cors do
+ allow do
+ origins '*'
+ resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete]
+ end
+ end
end
end
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index e7a9227e41..55ac088bc1 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -7,7 +7,7 @@ production:
reconnect: false
database: gitlabhq_production
pool: 10
- username: gitlab
+ username: git
password: "secure password"
# host: localhost
# socket: /tmp/mysql.sock
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 6cba17f6ea..e4c7649fda 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -6,9 +6,6 @@ Gitlab::Application.configure do
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
- # Log error messages when you accidentally call methods on nil.
- config.whiny_nils = true
-
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
@@ -25,10 +22,6 @@ Gitlab::Application.configure do
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
- config.active_record.auto_explain_threshold_in_seconds = 0.5
-
# Do not compress assets
config.assets.compress = false
@@ -39,4 +32,6 @@ Gitlab::Application.configure do
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# Open sent mails in browser
config.action_mailer.delivery_method = :letter_opener
+
+ config.eager_load = false
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index e3476be8fb..47f7e17aeb 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -33,6 +33,12 @@ Gitlab::Application.configure do
# See everything in the log (default is :info)
# config.log_level = :debug
+ # Suppress 'Rendered template ...' messages in the log
+ # source: http://stackoverflow.com/a/16369363
+ %w{render_template render_partial render_collection}.each do |event|
+ ActiveSupport::Notifications.unsubscribe "#{event}.action_view"
+ end
+
# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]
@@ -47,7 +53,7 @@ Gitlab::Application.configure do
else
"redis://localhost:6379"
end
- config.cache_store = :redis_store, resque_url
+ config.cache_store = :redis_store, resque_url, {namespace: 'cache:gitlab'}
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
@@ -80,4 +86,9 @@ Gitlab::Application.configure do
# # }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
+
+ config.eager_load = true
+ config.assets.js_compressor = :uglifier
+
+ config.allow_concurrency = false
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index b626986299..3860dc5c74 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -11,9 +11,6 @@ Gitlab::Application.configure do
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
- # Log error messages when you accidentally call methods on nil
- config.whiny_nils = true
-
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
@@ -34,4 +31,6 @@ Gitlab::Application.configure do
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
+
+ config.eager_load = false
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 163af226aa..07c9681f9a 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -14,18 +14,18 @@ production: &base
## GitLab settings
gitlab:
- ## Web server settings
+ ## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
port: 80
https: false
- # Uncomment and customize the last line to run in a non-root path
- # WARNING: This feature is known to work, but unsupported
- # Note that three settings need to be changed for this to work.
- # 1) In your application.rb file: config.relative_url_root = "/gitlab"
- # 2) In your gitlab.yml file: relative_url_root: /gitlab
- # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
- #
+ # Uncommment this line below if your ssh host is different from HTTP/HTTPS one
+ # (you'd obviously need to replace ssh.host_example.com with your own host).
+ # Otherwise, ssh host will be set to the `host:` value above
+ # ssh_host: ssh.host_example.com
+
+ # WARNING: See config/application.rb under "Relative url support" for the list of
+ # other files that need to be changed for relative url support
# relative_url_root: /gitlab
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
@@ -52,14 +52,22 @@ production: &base
## Users management
- # default: false - Account passwords are not sent via the email if signup is enabled.
+ # default: false - Account passwords are not sent via the email if signup is enabled.
# signup_enabled: true
+ #
+ # default: true - If set to false, standard login form won't be shown on the sign-in page
+ # signin_enabled: false
+
+
+ # Restrict setting visibility levels for non-admin users.
+ # The default is to allow all levels.
+ #restricted_visibility_levels: [ "public" ]
## Automatic issue closing
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
- # This happends when the commit is pushed or merged into the default branch of a project.
+ # This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used.
- # issue_closing_pattern: ([Cc]loses|[Ff]ixes) +#\d+
+ # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)'
## Default project features settings
default_projects_features:
@@ -68,7 +76,12 @@ production: &base
wiki: true
wall: false
snippets: false
- public: false
+ visibility_level: "private" # can be "private" | "internal" | "public"
+
+ ## Repository downloads directory
+ # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
+ # The default is 'tmp/repositories' relative to the root of the Rails app.
+ # repository_downloads_path: tmp/repositories
## External issues trackers
issues_tracker:
@@ -87,12 +100,12 @@ production: &base
# ## :id - Issue id (from commit messages)
# issues_url: "http://redmine.sample/issues/:id"
#
- # ## If not nil, linkis to creating new issues will be replaced with this
+ # ## If not nil, links to creating new issues will be replaced with this
# ## Use placeholders:
# ## :project_id - GitLab project identifier
# ## :issues_tracker_id - Project Name or Id in external issue tracker
# new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new"
- #
+ #
# jira:
# title: "Atlassian Jira"
# project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id"
@@ -102,6 +115,7 @@ production: &base
## Gravatar
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
+ # gravatar urls: possible placeholders: %{hash} %{size} %{email}
# 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
@@ -110,17 +124,41 @@ production: &base
# ==========================
## LDAP settings
+ # You can inspect a sample of the LDAP users with login access by running:
+ # bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
host: '_your_ldap_server'
- base: '_the_base_where_you_search_for_users'
port: 636
uid: 'sAMAccountName'
- method: 'ssl' # "ssl" or "plain"
+ method: 'ssl' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
allow_username_or_email_login: true
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515
+ # Ex. (employeeType=developer)
+ #
+ user_filter: ''
+
+
## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
@@ -136,9 +174,10 @@ production: &base
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
# If your favorite auth provider is not listed you can use others:
- # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
+ # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations
# The 'app_id' and 'app_secret' parameters are always passed as the first two
# arguments, followed by optional 'args' which can be either a hash or an array.
+ # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
# - { name: 'google_oauth2', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET',
@@ -146,7 +185,8 @@ production: &base
# - { name: 'twitter', app_id: 'YOUR APP ID',
# app_secret: 'YOUR APP SECRET'}
# - { name: 'github', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET' }
+ # app_secret: 'YOUR APP SECRET',
+ # args: { scope: 'user:email' } }
@@ -166,6 +206,8 @@ production: &base
## GitLab Shell settings
gitlab_shell:
+ path: /home/git/gitlab-shell/
+
# REPOS_PATH MUST NOT BE A SYMLINK!!!
repos_path: /home/git/repositories/
hooks_path: /home/git/gitlab-shell/hooks/
@@ -182,7 +224,8 @@ production: &base
# Use the default values unless you really know what you are doing
git:
bin_path: /usr/bin/git
- # Max size of a git object (e.g. a commit), in bytes
+ # The next value is the maximum memory size grit can use
+ # Given in number of bytes per git object (e.g. a commit)
# This value can be increased if you have very large commits
max_size: 5242880 # 5.megabytes
# Git timeout to read a commit, in seconds
@@ -196,6 +239,10 @@ production: &base
## Google analytics. Uncomment if you want it
# google_analytics_id: '_your_tracking_id'
+ ## Piwik analytics.
+ # piwik_url: '_your_piwik_url'
+ # piwik_site_id: '_your_piwik_site_id'
+
## Text under sign-in page (Markdown enabled)
# sign_in_text: |
# 
@@ -206,6 +253,11 @@ development:
test:
<<: *base
+ gravatar:
+ enabled: true
+ gitlab:
+ host: localhost
+ port: 80
issues_tracker:
redmine:
title: "Redmine"
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 942b77ffd2..97f2954640 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1,10 +1,10 @@
class Settings < Settingslogic
- source "#{Rails.root}/config/gitlab.yml"
+ source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" }
namespace Rails.env
class << self
- def gitlab_on_non_standard_port?
- ![443, 80].include?(gitlab.port.to_i)
+ def gitlab_on_standard_port?
+ gitlab.port.to_i == (gitlab.https ? 443 : 80)
end
private
@@ -18,11 +18,7 @@ class Settings < Settingslogic
end
def build_gitlab_url
- if gitlab_on_non_standard_port?
- custom_port = ":#{gitlab.port}"
- else
- custom_port = nil
- end
+ custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}"
[ gitlab.protocol,
"://",
gitlab.host,
@@ -30,6 +26,29 @@ class Settings < Settingslogic
gitlab.relative_url_root
].join('')
end
+
+ # check that values in `current` (string or integer) is a contant in `modul`.
+ def verify_constant_array(modul, current, default)
+ values = default || []
+ if !current.nil?
+ values = []
+ current.each do |constant|
+ values.push(verify_constant(modul, constant, nil))
+ end
+ values.delete_if { |value| value.nil? }
+ end
+ values
+ end
+
+ # check that `current` (string or integer) is a contant in `modul`.
+ def verify_constant(modul, current, default)
+ constant = modul.constants.find{ |name| modul.const_get(name) == current }
+ value = constant.nil? ? default : modul.const_get(constant)
+ if current.is_a? String
+ value = modul.const_get(current.upcase) rescue default
+ end
+ value
+ end
end
end
@@ -54,6 +73,7 @@ Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
+Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
@@ -68,15 +88,18 @@ rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
Settings.gitlab['signup_enabled'] ||= false
+Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
+Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
-Settings.gitlab.default_projects_features['public'] = false if Settings.gitlab.default_projects_features['public'].nil?
+Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
+Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
#
# Gravatar
@@ -90,11 +113,12 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?
# GitLab Shell
#
Settings['gitlab_shell'] ||= Settingslogic.new({})
+Settings.gitlab_shell['path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/'
Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/'
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
Settings.gitlab_shell['repos_path'] ||= Settings.gitlab['user_home'] + '/repositories/'
-Settings.gitlab_shell['ssh_host'] ||= (Settings.gitlab.host || 'localhost')
+Settings.gitlab_shell['ssh_host'] ||= Settings.gitlab.ssh_host
Settings.gitlab_shell['ssh_port'] ||= 22
Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user
Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index e2f9800234..655590dff0 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -1,6 +1,6 @@
module Gitlab
VERSION = File.read(Rails.root.join("VERSION")).strip
- REVISION = `git log --pretty=format:'%h' -n 1`
+ REVISION = Gitlab::Popen.popen(%W(git log --pretty=format:%h -n 1)).first.chomp
def self.config
Settings
diff --git a/config/initializers/3_grit_ext.rb b/config/initializers/3_grit_ext.rb
index 8b298e821e..6540ac839c 100644
--- a/config/initializers/3_grit_ext.rb
+++ b/config/initializers/3_grit_ext.rb
@@ -1,5 +1,4 @@
require 'grit'
-require 'pygments'
Grit::Git.git_binary = Gitlab.config.git.bin_path
Grit::Git.git_timeout = Gitlab.config.git.timeout
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
new file mode 100644
index 0000000000..a7ee3c5982
--- /dev/null
+++ b/config/initializers/6_rack_profiler.rb
@@ -0,0 +1,6 @@
+if Rails.env == 'development'
+ require 'rack-mini-profiler'
+
+ # initialization is skipped so trigger it
+ Rack::MiniProfilerRails.initialize!(Rails.application)
+end
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index 45bc68f322..d0065b63e5 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -15,5 +15,19 @@ if File.exists?(aws_file)
config.fog_directory = AWS_CONFIG['bucket'] # required
config.fog_public = false # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
+ config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid.
+ # when fog_public is false and provider is AWS or Google, defaults to 600
+ end
+
+ # Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders
+ if Rails.env.test?
+ Fog.mock!
+ connection = ::Fog::Storage.new(
+ :aws_access_key_id => AWS_CONFIG['access_key_id'],
+ :aws_secret_access_key => AWS_CONFIG['secret_access_key'],
+ :provider => 'AWS',
+ :region => AWS_CONFIG['region']
+ )
+ connection.directories.create(:key => AWS_CONFIG['bucket'])
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index b7cb808d2e..d5cb110e88 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -4,7 +4,8 @@ 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.gitlab.email_from
+ config.mailer_sender = "GitLab <#{Gitlab.config.gitlab.email_from}>"
+
# Configure the class responsible to send e-mails.
# config.mailer = "Devise::Mailer"
@@ -74,13 +75,13 @@ Devise.setup do |config|
# config.pepper = "2ef62d549c4ff98a5d3e0ba211e72cff592060247e3bbbb9f499af1222f876f53d39b39b823132affb32858168c79c1d7741d26499901b63c6030a42129924ef"
# ==> Configuration for :confirmable
- # The time you want to give your user to confirm his account. During this time
- # he will be able to access your application without confirming. Default is 0.days
- # When confirm_within is zero, the user won't be able to sign in without confirming.
+ # The time you want to give a user to confirm their account. During this time
+ # they will be able to access your application without confirming. Default is 0.days
+ # When allow_unconfirmed_access_for is zero, the user won't be able to sign in without confirming.
# You can use this to let your user access some features of your application
# without confirming the account, but blocking it after a certain period
# (ie 2 days).
- # config.confirm_within = 2.days
+ # config.allow_unconfirmed_access_for = 2.days
# Defines which key will be used when confirming an account
# config.confirmation_keys = [ :email ]
@@ -101,7 +102,7 @@ Devise.setup do |config|
# ==> Configuration for :validatable
# Range for password length. Default is 6..128.
- config.password_length = 6..128
+ config.password_length = 8..128
# Email regex used to validate email formats. It simply asserts that
# an one (and only one) @ exists in the given string. This is mainly
@@ -222,19 +223,26 @@ Devise.setup do |config|
method: Gitlab.config.ldap['method'],
bind_dn: Gitlab.config.ldap['bind_dn'],
password: Gitlab.config.ldap['password'],
+ filter: Gitlab.config.ldap['user_filter'],
name_proc: email_stripping_proc
end
Gitlab.config.omniauth.providers.each do |provider|
+ provider_arguments = []
+
+ %w[app_id app_secret].each do |argument|
+ provider_arguments << provider[argument] if provider[argument]
+ end
+
case provider['args']
when Array
# An Array from the configuration will be expanded.
- config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'], *provider['args']
+ provider_arguments.concat provider['args']
when Hash
# A Hash from the configuration will be passed as is.
- config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret'], provider['args']
- else
- config.omniauth provider['name'].to_sym, provider['app_id'], provider['app_secret']
+ provider_arguments << provider['args']
end
+
+ config.omniauth provider['name'].to_sym, *provider_arguments
end
-end
+end
\ No newline at end of file
diff --git a/config/initializers/devise_password_length.rb.example b/config/initializers/devise_password_length.rb.example
new file mode 100644
index 0000000000..97305825e0
--- /dev/null
+++ b/config/initializers/devise_password_length.rb.example
@@ -0,0 +1,6 @@
+Devise.setup do |config|
+ # The following line changes the password length limits for new users. In the
+ # example below the minimum length is 12 characters, and the maximum length
+ # is 128 characters.
+ config.password_length = 12..128
+end
diff --git a/config/initializers/gemoji.rb b/config/initializers/gemoji.rb
deleted file mode 100644
index 8c85aad5d3..0000000000
--- a/config/initializers/gemoji.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-# Workaround for https://github.com/github/gemoji/pull/18
-Gitlab::Application.config.assets.paths << Emoji.images_path
diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example
index 76fa7ad282..bc3234bf0b 100644
--- a/config/initializers/rack_attack.rb.example
+++ b/config/initializers/rack_attack.rb.example
@@ -1,16 +1,18 @@
-# To enable rack-attack for your GitLab instance do the following:
-# 1. In config/application.rb find and uncomment the following line:
-# config.middleware.use Rack::Attack
-# 2. Rename this file to rack_attack.rb
-# 3. Review the paths_to_be_protected and add any other path you need protecting
-# 4. Restart GitLab instance
+# 1. Rename this file to rack_attack.rb
+# 2. Review the paths_to_be_protected and add any other path you need protecting
#
paths_to_be_protected = [
"#{Rails.application.config.relative_url_root}/users/password",
"#{Rails.application.config.relative_url_root}/users/sign_in",
- "#{Rails.application.config.relative_url_root}/users"
+ "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session.json",
+ "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session",
+ "#{Rails.application.config.relative_url_root}/users",
+ "#{Rails.application.config.relative_url_root}/users/confirmation"
]
-Rack::Attack.throttle('protected paths', limit: 6, period: 60.seconds) do |req|
- req.ip if paths_to_be_protected.include?(req.path) && req.post?
+
+unless Rails.env.test?
+ Rack::Attack.throttle('protected paths', limit: 10, period: 60.seconds) do |req|
+ req.ip if paths_to_be_protected.include?(req.path) && req.post?
+ end
end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 16d1d4a9fd..62a54bc8c6 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -9,7 +9,9 @@ require 'securerandom'
def find_secure_token
token_file = Rails.root.join('.secret')
- if File.exist? token_file
+ if ENV.key?('SECRET_KEY_BASE')
+ ENV['SECRET_KEY_BASE']
+ elsif File.exist? token_file
# Use the existing token.
File.read(token_file).chomp
else
@@ -21,3 +23,4 @@ def find_secure_token
end
Gitlab::Application.config.secret_token = find_secure_token
+Gitlab::Application.config.secret_key_base = find_secure_token
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 467f947739..5fe5270236 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -2,9 +2,9 @@
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
- servers: Gitlab::Application.config.cache_store.last, # re-use the Redis config from the Rails cache store
+ servers: Gitlab::Application.config.cache_store[1], # re-use the Redis config from the Rails cache store
key: '_gitlab_session',
- secure: Gitlab::Application.config.force_ssl,
+ secure: Gitlab.config.gitlab.https,
httponly: true,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
)
diff --git a/config/routes.rb b/config/routes.rb
index 78f75d1183..7641fe4308 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,11 +6,15 @@ Gitlab::Application.routes.draw do
# Search
#
get 'search' => "search#show"
+ get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete
# API
API::API.logger Rails.logger
mount API::API => '/api'
+ # Get all keys of user
+ get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
+
constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
mount Sidekiq::Web, at: "/admin/sidekiq", as: :sidekiq
@@ -22,7 +26,7 @@ Gitlab::Application.routes.draw do
project_root: Gitlab.config.gitlab_shell.repos_path,
upload_pack: Gitlab.config.gitlab_shell.upload_pack,
receive_pack: Gitlab.config.gitlab_shell.receive_pack
- }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }
+ }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post]
#
# Help
@@ -86,14 +90,19 @@ Gitlab::Application.routes.draw do
get :test
end
+ resources :broadcast_messages, only: [:index, :create, :destroy]
resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
- resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show]
+
+ resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
+ member do
+ put :transfer
+ end
+ end
+
root to: "dashboard#index"
end
- get "errors/githost"
-
#
# Profile Area
#
@@ -115,15 +124,17 @@ Gitlab::Application.routes.draw do
end
end
resources :keys
+ resources :emails, only: [:index, :create, :destroy]
resources :groups, only: [:index] do
member do
delete :leave
end
end
+ resource :avatar, only: [:destroy]
end
end
- match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }
+ match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
@@ -149,11 +160,14 @@ Gitlab::Application.routes.draw do
end
resources :users_groups, only: [:create, :update, :destroy]
+ scope module: :groups do
+ resource :avatar, only: [:destroy]
+ end
end
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
- devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations }
+ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords}
#
# Project Area
@@ -162,20 +176,28 @@ Gitlab::Application.routes.draw do
member do
put :transfer
post :fork
+ post :archive
+ post :unarchive
get :autocomplete_sources
+ get :import
+ put :retry_import
end
scope module: :projects do
- resources :blob, only: [:show], constraints: {id: /.+/}
- resources :raw, only: [:show], constraints: {id: /.+/}
- resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
- resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit'
- resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
- resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
- resources :compare, only: [:index, :create]
- resources :blame, only: [:show], constraints: {id: /.+/}
+ resources :blob, only: [:show, :destroy], constraints: {id: /.+/}
+ resources :raw, only: [:show], constraints: {id: /.+/}
+ resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
+ resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
+ post :preview, on: :member
+ end
+ resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
+ resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
+ resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
+ resources :compare, only: [:index, :create]
+ resources :blame, only: [:show], constraints: {id: /.+/}
resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
- resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+ resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
+
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
resources :snippets, constraints: {id: /\d+/} do
@@ -184,7 +206,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-]+/} do
+ resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
collection do
get :pages
put ':id' => 'wikis#update'
@@ -205,7 +227,7 @@ Gitlab::Application.routes.draw do
resource :repository, only: [:show] do
member do
get "stats"
- get "archive"
+ get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
end
end
@@ -251,7 +273,7 @@ Gitlab::Application.routes.draw do
resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
member do
get :diffs
- get :automerge
+ post :automerge
get :automerge_check
get :ci_status
end
@@ -286,6 +308,7 @@ Gitlab::Application.routes.draw do
resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
collection do
+ delete :leave
# Used for import team
# from another project
@@ -306,5 +329,7 @@ Gitlab::Application.routes.draw do
end
end
+ get ':id' => "groups#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
+
root to: "dashboard#show"
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index e4e1342683..f6c0f09b51 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -8,12 +8,8 @@
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
-# Uncomment and customize the last line to run in a non-root path
-# WARNING: This feature is known to work, but unsupported
-# Note that three settings need to be changed for this to work.
-# 1) In your application.rb file: config.relative_url_root = "/gitlab"
-# 2) In your gitlab.yml file: relative_url_root: /gitlab
-# 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
+# WARNING: See config/application.rb under "Relative url support" for the list of
+# other files that need to be changed for relative url support
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
new file mode 100644
index 0000000000..94a7061451
--- /dev/null
+++ b/config/unicorn.rb.example.development
@@ -0,0 +1,2 @@
+worker_processes 2
+timeout 30
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 3e76d76e83..42d1843534 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -8,8 +8,7 @@ User.seed(:id, [
password_confirmation: "5iveL!fe",
admin: true,
projects_limit: 100,
- theme_id: Gitlab::Theme::MARS
+ theme_id: Gitlab::Theme::MARS,
+ confirmed_at: DateTime.now
}
])
-
-User.find(1).confirm!
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 6fdb163c27..164bb63780 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,51 +1,56 @@
-project_urls = [
- 'https://github.com/documentcloud/underscore.git',
- 'https://github.com/diaspora/diaspora.git',
- 'https://github.com/diaspora/diaspora-project-site.git',
- 'https://github.com/diaspora/diaspora-client.git',
- 'https://github.com/brightbox/brightbox-cli.git',
- 'https://github.com/brightbox/puppet.git',
- 'https://github.com/gitlabhq/gitlabhq.git',
- 'https://github.com/gitlabhq/gitlab-ci.git',
- 'https://github.com/gitlabhq/gitlab-recipes.git',
- 'https://github.com/gitlabhq/gitlab-shell.git',
- 'https://github.com/gitlabhq/grack.git',
- 'https://github.com/twitter/flight.git',
- 'https://github.com/twitter/typeahead.js.git',
- 'https://github.com/h5bp/html5-boilerplate.git',
- 'https://github.com/h5bp/mobile-boilerplate.git',
-]
+Gitlab::Seeder.quiet do
+ project_urls = [
+ 'https://github.com/documentcloud/underscore.git',
+ 'https://github.com/diaspora/diaspora.git',
+ 'https://github.com/diaspora/diaspora-project-site.git',
+ 'https://github.com/diaspora/diaspora-client.git',
+ 'https://github.com/brightbox/brightbox-cli.git',
+ 'https://github.com/brightbox/puppet.git',
+ 'https://github.com/gitlabhq/gitlabhq.git',
+ 'https://github.com/gitlabhq/gitlab-ci.git',
+ 'https://github.com/gitlabhq/gitlab-recipes.git',
+ 'https://github.com/gitlabhq/gitlab-shell.git',
+ 'https://github.com/gitlabhq/grack.git',
+ 'https://github.com/gitlabhq/testme.git',
+ 'https://github.com/twitter/flight.git',
+ 'https://github.com/twitter/typeahead.js.git',
+ 'https://github.com/h5bp/html5-boilerplate.git',
+ 'https://github.com/h5bp/mobile-boilerplate.git',
+ ]
-project_urls.each_with_index do |url, i|
- group_path, project_path = url.split('/')[-2..-1]
+ project_urls.each_with_index do |url, i|
+ group_path, project_path = url.split('/')[-2..-1]
- group = Group.find_by_path(group_path)
+ group = Group.find_by(path: group_path)
- unless group
- group = Group.new(
- name: group_path.titleize,
- path: group_path
- )
- group.description = Faker::Lorem.sentence
- group.owner = User.first
- group.save
- end
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.description = Faker::Lorem.sentence
+ group.save
- project_path.gsub!(".git", "")
+ group.add_owner(User.first)
+ end
- params = {
- import_url: url,
- namespace_id: group.id,
- name: project_path.titleize,
- description: Faker::Lorem.sentence
- }
+ project_path.gsub!(".git", "")
- project = Projects::CreateContext.new(User.first, params).execute
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize,
+ description: Faker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample
+ }
- if project.valid?
- print '.'
- else
- puts project.errors.full_messages
- print 'F'
+ project = Projects::CreateService.new(User.first, params).execute
+
+ if project.valid?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
end
end
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index cbb3e636ac..d736408dbf 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,11 +1,12 @@
Gitlab::Seeder.quiet do
- (2..50).each do |i|
+ (2..10).each do |i|
begin
User.seed(:id, [{
id: i,
username: Faker::Internet.user_name,
name: Faker::Name.name,
email: Faker::Internet.email,
+ confirmed_at: DateTime.now
}])
print '.'
rescue ActiveRecord::RecordNotSaved
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index a1e01879db..dfbe75fd20 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
Group.all.each do |group|
User.all.sample(4).each do |user|
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index 6fe7e24677..72e6b3ad3d 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,3 +1,5 @@
+ActiveRecord::Base.observers.disable(:milestone_observer)
+
Milestone.seed(:id, [
{ id: 1, project_id: 1, title: 'v' + Faker::Address.zip_code },
{ id: 2, project_id: 1, title: 'v' + Faker::Address.zip_code },
@@ -16,3 +18,5 @@ Milestone.all.map do |ml|
ml.set_iid
ml.save
end
+
+ActiveRecord::Base.observers.enable(:milestone_observer)
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 85e309b633..635878622d 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,20 +1,16 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
(1..300).each do |i|
# Random Project
project = Project.all.sample
# Random user
- user = project.users.sample
+ user = project.team.users.sample
next unless user
user_id = user.id
- begin
- Thread.current[:current_user] = user
-
+ Gitlab::Seeder.by_user(user) do
Issue.seed(:id, [{
id: i,
project_id: project.id,
@@ -25,8 +21,6 @@ Gitlab::Seeder.quiet do
title: Faker::Lorem.sentence(6),
description: Faker::Lorem.sentence
}])
- ensure
- Thread.current[:current_user] = nil
end
print('.')
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 75f6e56985..62fd0d84ea 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,50 +1,33 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
- (1..100).each do |i|
- # Random Project
- project = Project.all.sample
+ Project.all.reject(&:empty_repo?).each do |project|
+ branches = project.repository.branch_names
- # Random user
- user = project.users.sample
+ branches.each do |branch_name|
+ break if branches.size < 2
+ source_branch = branches.pop
+ target_branch = branches.pop
- next unless user
+ # Random user
+ user = project.team.users.sample
+ next unless user
- next if project.empty_repo?
+ params = {
+ source_branch: source_branch,
+ target_branch: target_branch,
+ title: Faker::Lorem.sentence(6),
+ description: Faker::Lorem.sentences(3).join(" ")
+ }
- branches = project.repository.branch_names.sample(2)
+ merge_request = MergeRequests::CreateService.new(project, user, params).execute
- next if branches.uniq.size < 2
-
- user_id = user.id
- begin
- Thread.current[:current_user] = user
-
- MergeRequest.seed(:id, [{
- id: i,
- source_branch: branches.first,
- target_branch: branches.last,
- source_project_id: project.id,
- target_project_id: project.id,
- author_id: user_id,
- assignee_id: user_id,
- milestone: project.milestones.sample,
- title: Faker::Lorem.sentence(6)
- }])
- ensure
- Thread.current[:current_user] = nil
+ if merge_request.valid?
+ merge_request.assignee = user
+ merge_request.milestone = project.milestones.sample
+ merge_request.save
+ print '.'
+ else
+ print 'F'
+ end
end
- print('.')
end
end
-
-MergeRequest.all.map do |mr|
- mr.set_iid
- mr.save
-end
-
-puts 'Load diffs for Merge Requests (it will take some time)...'
-MergeRequest.all.each do |mr|
- mr.reload_code
- print '.'
-end
diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb
index 4b53ff411f..61329f197c 100644
--- a/db/fixtures/development/11_keys.rb
+++ b/db/fixtures/development/11_keys.rb
@@ -1,10 +1,8 @@
-ActiveRecord::Base.observers.enable :all
-
Gitlab::Seeder.quiet do
User.first(30).each_with_index do |user, i|
Key.seed(:id, [
{
- id: i,
+ id: i + 1,
title: "Sample key #{i}",
key: "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{i + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
user_id: user.id,
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index 4ca8afe294..dced270626 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,5 +1,3 @@
-ActiveRecord::Base.observers.disable :all
-
Gitlab::Seeder.quiet do
contents = [
`curl https://gist.github.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb
new file mode 100644
index 0000000000..626aba200d
--- /dev/null
+++ b/db/fixtures/development/13_comments.rb
@@ -0,0 +1,19 @@
+Gitlab::Seeder.quiet do
+ Issue.all.limit(10).each_with_index do |issue, i|
+ 5.times do
+ user = issue.project.team.users.sample
+
+ Gitlab::Seeder.by_user(user) do
+ Note.seed(:id, [{
+ project_id: issue.project.id,
+ author_id: user.id,
+ note: Faker::Lorem.sentence,
+ noteable_id: issue.id,
+ noteable_type: 'Issue'
+ }])
+
+ print '.'
+ end
+ end
+ end
+end
diff --git a/db/migrate/20110913200833_devise_create_users.rb b/db/migrate/20110913200833_devise_create_users.rb
deleted file mode 100644
index e00f275c55..0000000000
--- a/db/migrate/20110913200833_devise_create_users.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-class DeviseCreateUsers < ActiveRecord::Migration
- def self.up
- create_table(:users) do |t|
- ## Database authenticatable
- t.string :email, :null => false, :default => ""
- t.string :encrypted_password, :null => false, :default => ""
-
- ## Recoverable
- t.string :reset_password_token
- t.datetime :reset_password_sent_at
-
- ## Rememberable
- t.datetime :remember_created_at
-
- ## Trackable
- t.integer :sign_in_count, :default => 0
- t.datetime :current_sign_in_at
- t.datetime :last_sign_in_at
- t.string :current_sign_in_ip
- t.string :last_sign_in_ip
-
- ## Encryptable
- # t.string :password_salt
-
- ## Confirmable
- # t.string :confirmation_token
- # t.datetime :confirmed_at
- # t.datetime :confirmation_sent_at
- # t.string :unconfirmed_email # Only if using reconfirmable
-
- ## Lockable
- # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
- # t.string :unlock_token # Only if unlock strategy is :email or :both
- # t.datetime :locked_at
-
- # Token authenticatable
- # t.string :authentication_token
-
- ## Invitable
- # t.string :invitation_token
-
- t.timestamps
- end
-
- add_index :users, :email, :unique => true
- add_index :users, :reset_password_token, :unique => true
- # add_index :users, :confirmation_token, :unique => true
- # add_index :users, :unlock_token, :unique => true
- # add_index :users, :authentication_token, :unique => true
- end
-
- def self.down
- drop_table :users
- end
-end
diff --git a/db/migrate/20110913204141_create_projects.rb b/db/migrate/20110913204141_create_projects.rb
deleted file mode 100644
index 45b76f9556..0000000000
--- a/db/migrate/20110913204141_create_projects.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateProjects < ActiveRecord::Migration
- def change
- create_table :projects do |t|
- t.string :name
- t.string :path
- t.text :description
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20110914221600_create_users_projects.rb b/db/migrate/20110914221600_create_users_projects.rb
deleted file mode 100644
index a89798ae86..0000000000
--- a/db/migrate/20110914221600_create_users_projects.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class CreateUsersProjects < ActiveRecord::Migration
- def change
- create_table :users_projects do |t|
- t.integer :user_id, :null => false
- t.integer :project_id, :null => false
- t.boolean :read, :default => false
- t.boolean :write, :default => false
- t.boolean :admin, :default => false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20110915205627_add_private_flag_to_project.rb b/db/migrate/20110915205627_add_private_flag_to_project.rb
deleted file mode 100644
index 73c0b9df83..0000000000
--- a/db/migrate/20110915205627_add_private_flag_to_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddPrivateFlagToProject < ActiveRecord::Migration
- def change
- add_column :projects, :private_flag, :boolean, :default => true, :null => false
- end
-end
diff --git a/db/migrate/20110915213352_create_keys.rb b/db/migrate/20110915213352_create_keys.rb
deleted file mode 100644
index d4615b4bab..0000000000
--- a/db/migrate/20110915213352_create_keys.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class CreateKeys < ActiveRecord::Migration
- def change
- create_table :keys do |t|
- t.integer :user_id, :null => false
- t.text :project_id, :null => false
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20110916123731_add_name_to_user.rb b/db/migrate/20110916123731_add_name_to_user.rb
deleted file mode 100644
index 74142b05cb..0000000000
--- a/db/migrate/20110916123731_add_name_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddNameToUser < ActiveRecord::Migration
- def change
- add_column :users, :name, :string
- end
-end
diff --git a/db/migrate/20110916162511_add_key_title_to_key.rb b/db/migrate/20110916162511_add_key_title_to_key.rb
deleted file mode 100644
index b2eaa51ccc..0000000000
--- a/db/migrate/20110916162511_add_key_title_to_key.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AddKeyTitleToKey < ActiveRecord::Migration
- def change
- add_column :keys, :key, :text
- add_column :keys, :title, :string
- remove_column :keys, :project_id
- end
-end
diff --git a/db/migrate/20110917212932_add_identifier_to_key.rb b/db/migrate/20110917212932_add_identifier_to_key.rb
deleted file mode 100644
index e572793952..0000000000
--- a/db/migrate/20110917212932_add_identifier_to_key.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddIdentifierToKey < ActiveRecord::Migration
- def change
- add_column :keys, :identifier, :string
- end
-end
diff --git a/db/migrate/20110921192501_create_issues.rb b/db/migrate/20110921192501_create_issues.rb
deleted file mode 100644
index 63b42ad998..0000000000
--- a/db/migrate/20110921192501_create_issues.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class CreateIssues < ActiveRecord::Migration
- def change
- create_table :issues do |t|
- t.string :title
- t.text :content
- t.integer :assignee_id
- t.integer :author_id
- t.integer :project_id
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20110922110156_add_code_to_project.rb b/db/migrate/20110922110156_add_code_to_project.rb
deleted file mode 100644
index f54a02bd70..0000000000
--- a/db/migrate/20110922110156_add_code_to_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddCodeToProject < ActiveRecord::Migration
- def change
- add_column :projects, :code, :string
- end
-end
diff --git a/db/migrate/20110923211333_add_status_to_issue.rb b/db/migrate/20110923211333_add_status_to_issue.rb
deleted file mode 100644
index c53bf46ef7..0000000000
--- a/db/migrate/20110923211333_add_status_to_issue.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddStatusToIssue < ActiveRecord::Migration
- def change
- add_column :issues, :closed, :boolean, :default => false, :null => false
- end
-end
diff --git a/db/migrate/20110924214549_create_rails_admin_histories_table.rb b/db/migrate/20110924214549_create_rails_admin_histories_table.rb
deleted file mode 100644
index 3c743aa287..0000000000
--- a/db/migrate/20110924214549_create_rails_admin_histories_table.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-class CreateRailsAdminHistoriesTable < ActiveRecord::Migration
- def self.up
- create_table :rails_admin_histories do |t|
- t.text :message # title, name, or object_id
- t.string :username
- t.integer :item
- t.string :table
- t.integer :month, :limit => 2
- t.integer :year, :limit => 5
- t.timestamps
- end
- add_index(:rails_admin_histories, [:item, :table, :month, :year], :name => 'index_rails_admin_histories' )
- end
-
- def self.down
- drop_table :rails_admin_histories
- end
-end
diff --git a/db/migrate/20110924215658_add_admin_field_to_user.rb b/db/migrate/20110924215658_add_admin_field_to_user.rb
deleted file mode 100644
index 321587e695..0000000000
--- a/db/migrate/20110924215658_add_admin_field_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddAdminFieldToUser < ActiveRecord::Migration
- def change
- add_column :users, :admin, :boolean, :default => false, :null => false
- end
-end
diff --git a/db/migrate/20110926082616_remove_admin.rb b/db/migrate/20110926082616_remove_admin.rb
deleted file mode 100644
index aac9ff7888..0000000000
--- a/db/migrate/20110926082616_remove_admin.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RemoveAdmin < ActiveRecord::Migration
- def up
- drop_table :rails_admin_histories
- end
-
- def down
- raise "No rollback"
- end
-end
diff --git a/db/migrate/20110927130352_create_notes.rb b/db/migrate/20110927130352_create_notes.rb
deleted file mode 100644
index 72a0e81761..0000000000
--- a/db/migrate/20110927130352_create_notes.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class CreateNotes < ActiveRecord::Migration
- def change
- create_table :notes do |t|
- t.string :note
- t.integer :noteable_id
- t.string :noteable_type
- t.integer :author_id
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20110928140106_add_project_id_for_note.rb b/db/migrate/20110928140106_add_project_id_for_note.rb
deleted file mode 100644
index 3e64108979..0000000000
--- a/db/migrate/20110928140106_add_project_id_for_note.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class AddProjectIdForNote < ActiveRecord::Migration
- def up
- add_column :notes, :project_id, :integer
- end
-
- def down
- remove_column :notes, :project_id, :integer
- end
-end
diff --git a/db/migrate/20110928142747_change_noteable_id_for_note.rb b/db/migrate/20110928142747_change_noteable_id_for_note.rb
deleted file mode 100644
index dc9d1f0171..0000000000
--- a/db/migrate/20110928142747_change_noteable_id_for_note.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class ChangeNoteableIdForNote < ActiveRecord::Migration
- def up
- change_column :notes, :noteable_id, :string
- end
-
- def down
- change_column :notes, :noteable_id, :integer
- end
-end
diff --git a/db/migrate/20110928161328_add_attachment_to_note.rb b/db/migrate/20110928161328_add_attachment_to_note.rb
deleted file mode 100644
index 37d9cf1025..0000000000
--- a/db/migrate/20110928161328_add_attachment_to_note.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddAttachmentToNote < ActiveRecord::Migration
- def change
- add_column :notes, :attachment, :string
- end
-end
diff --git a/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb b/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb
deleted file mode 100644
index 82bd94b808..0000000000
--- a/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class AddAllowRepoCreationForUser < ActiveRecord::Migration
- def up
- add_column :users, :allowed_create_repo, :boolean, :default => true, :null => false
- end
-
- def down
- remove_column :users, :allowed_create_repo
- end
-end
diff --git a/db/migrate/20111009101738_add_ownerto_project.rb b/db/migrate/20111009101738_add_ownerto_project.rb
deleted file mode 100644
index 8d265533ad..0000000000
--- a/db/migrate/20111009101738_add_ownerto_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddOwnertoProject < ActiveRecord::Migration
- def change
- add_column :projects, :owner_id, :integer
- end
-end
diff --git a/db/migrate/20111009110913_add_projects_limit_to_user.rb b/db/migrate/20111009110913_add_projects_limit_to_user.rb
deleted file mode 100644
index 8eb8382b51..0000000000
--- a/db/migrate/20111009110913_add_projects_limit_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddProjectsLimitToUser < ActiveRecord::Migration
- def change
- add_column :users, :projects_limit, :integer, :default => 10
- end
-end
diff --git a/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb b/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb
deleted file mode 100644
index 6145200639..0000000000
--- a/db/migrate/20111009111204_remove_allow_create_repo_from_user.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RemoveAllowCreateRepoFromUser < ActiveRecord::Migration
- def up
- remove_column :users, :allowed_create_repo
- end
-
- def down
- add_column :users, :allowed_create_repo, :boolean, :default => true, :null => false
- end
-end
diff --git a/db/migrate/20111015154310_add_position_to_issues.rb b/db/migrate/20111015154310_add_position_to_issues.rb
deleted file mode 100644
index 41451a0cab..0000000000
--- a/db/migrate/20111015154310_add_position_to_issues.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddPositionToIssues < ActiveRecord::Migration
- def change
- add_column :issues, :position, :integer, :default => 0
- end
-end
diff --git a/db/migrate/20111016183422_create_snippets.rb b/db/migrate/20111016183422_create_snippets.rb
deleted file mode 100644
index 9b0bf201cb..0000000000
--- a/db/migrate/20111016183422_create_snippets.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class CreateSnippets < ActiveRecord::Migration
- def change
- create_table :snippets do |t|
- t.string :title
- t.text :content
- t.integer :author_id, :null => false
- t.integer :project_id, :null => false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20111016193417_add_content_type_to_snippets.rb b/db/migrate/20111016193417_add_content_type_to_snippets.rb
deleted file mode 100644
index 511a6793f4..0000000000
--- a/db/migrate/20111016193417_add_content_type_to_snippets.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddContentTypeToSnippets < ActiveRecord::Migration
- def change
- add_column :snippets, :content_type, :string, :null => false, :default => "txt"
- end
-end
diff --git a/db/migrate/20111016195506_add_file_name_to_snippets.rb b/db/migrate/20111016195506_add_file_name_to_snippets.rb
deleted file mode 100644
index d378d225ec..0000000000
--- a/db/migrate/20111016195506_add_file_name_to_snippets.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddFileNameToSnippets < ActiveRecord::Migration
- def change
- add_column :snippets, :file_name, :string
- remove_column :snippets, :content_type
- end
-end
diff --git a/db/migrate/20111019212429_add_social_to_user.rb b/db/migrate/20111019212429_add_social_to_user.rb
deleted file mode 100644
index b0ffe5366a..0000000000
--- a/db/migrate/20111019212429_add_social_to_user.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AddSocialToUser < ActiveRecord::Migration
- def change
- add_column :users, :skype, :string
- add_column :users, :linkedin, :string
- add_column :users, :twitter, :string
- end
-end
diff --git a/db/migrate/20111021101550_change_social_fields_in_users.rb b/db/migrate/20111021101550_change_social_fields_in_users.rb
deleted file mode 100644
index 6e506c1cf9..0000000000
--- a/db/migrate/20111021101550_change_social_fields_in_users.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class ChangeSocialFieldsInUsers < ActiveRecord::Migration
- def up
- remove_column :users, :skype
- remove_column :users, :linkedin
- remove_column :users, :twitter
-
- add_column :users, :skype, :string, {:null => false, :default => ''}
- add_column :users, :linkedin, :string, {:null => false, :default => ''}
- add_column :users, :twitter, :string, {:null => false, :default => ''}
- end
-
- def down
- end
-end
diff --git a/db/migrate/20111025134235_add_high_label_to_issue.rb b/db/migrate/20111025134235_add_high_label_to_issue.rb
deleted file mode 100644
index 676eaaf8c1..0000000000
--- a/db/migrate/20111025134235_add_high_label_to_issue.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddHighLabelToIssue < ActiveRecord::Migration
- def change
- add_column :issues, :critical, :boolean, :default => false, :null => false
- end
-end
diff --git a/db/migrate/20111027051828_add_expires_at_to_snippets.rb b/db/migrate/20111027051828_add_expires_at_to_snippets.rb
deleted file mode 100644
index 0d94b33376..0000000000
--- a/db/migrate/20111027051828_add_expires_at_to_snippets.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddExpiresAtToSnippets < ActiveRecord::Migration
- def change
- add_column :snippets, :expires_at, :datetime
- end
-end
diff --git a/db/migrate/20111027142641_change_note_note_to_text.rb b/db/migrate/20111027142641_change_note_note_to_text.rb
deleted file mode 100644
index d762d361ae..0000000000
--- a/db/migrate/20111027142641_change_note_note_to_text.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class ChangeNoteNoteToText < ActiveRecord::Migration
- def up
- change_column :notes, :note, :text
- end
-
- def down
- end
-end
diff --git a/db/migrate/20111027152724_issue_conten_to_note.rb b/db/migrate/20111027152724_issue_conten_to_note.rb
deleted file mode 100644
index 0677fee6b9..0000000000
--- a/db/migrate/20111027152724_issue_conten_to_note.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-class IssueContenToNote < ActiveRecord::Migration
- def up
- puts "Issue content is deprecated -> move to notes"
- Issue.find_each(:batch_size => 100) do |issue|
- next if issue.content.blank?
- note = Note.new(
- :note => issue.content,
- :project_id => issue.project_id,
- :noteable => issue,
- :created_at => issue.created_at,
- :updated_at => issue.created_at
- )
- note.author_id = issue.author_id
-
- if note.save
- issue.update_attributes(:content => nil)
- print "."
- else
- print "F"
- end
- end
-
- total = Issue.where("content is not null").count
-
- if total > 0
- puts "content of #{total} issues were not migrated"
- else
- puts "Done"
- end
- end
-
- def down
- end
-end
diff --git a/db/migrate/20111101222453_acts_as_taggable_on_migration.rb b/db/migrate/20111101222453_acts_as_taggable_on_migration.rb
deleted file mode 100644
index 16610615f9..0000000000
--- a/db/migrate/20111101222453_acts_as_taggable_on_migration.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-class ActsAsTaggableOnMigration < ActiveRecord::Migration
- def self.up
- create_table :tags do |t|
- t.string :name
- end
-
- create_table :taggings do |t|
- t.references :tag
-
- # You should make sure that the column created is
- # long enough to store the required class names.
- t.references :taggable, :polymorphic => true
- t.references :tagger, :polymorphic => true
-
- t.string :context
-
- t.datetime :created_at
- end
-
- add_index :taggings, :tag_id
- add_index :taggings, [:taggable_id, :taggable_type, :context]
- end
-
- def self.down
- drop_table :taggings
- drop_table :tags
- end
-end
diff --git a/db/migrate/20111111093150_remove_content_from_issues.rb b/db/migrate/20111111093150_remove_content_from_issues.rb
deleted file mode 100644
index 30bcdfb543..0000000000
--- a/db/migrate/20111111093150_remove_content_from_issues.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RemoveContentFromIssues < ActiveRecord::Migration
- def up
- remove_column :issues, :content
- end
-
- def down
- add_column :issues, :content, :text
- end
-end
diff --git a/db/migrate/20111115063954_add_authentication_token_to_users.rb b/db/migrate/20111115063954_add_authentication_token_to_users.rb
deleted file mode 100644
index 84433656d6..0000000000
--- a/db/migrate/20111115063954_add_authentication_token_to_users.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddAuthenticationTokenToUsers < ActiveRecord::Migration
- def change
- add_column :users, :authentication_token, :string
- end
-end
diff --git a/db/migrate/20111124115339_add_extra_field_to_issue.rb b/db/migrate/20111124115339_add_extra_field_to_issue.rb
deleted file mode 100644
index 4946c6fb0b..0000000000
--- a/db/migrate/20111124115339_add_extra_field_to_issue.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddExtraFieldToIssue < ActiveRecord::Migration
- def change
- add_column :issues, :branch_name, :string, :null => true
- end
-end
diff --git a/db/migrate/20111127155345_create_merge_requests.rb b/db/migrate/20111127155345_create_merge_requests.rb
deleted file mode 100644
index 1555ae8404..0000000000
--- a/db/migrate/20111127155345_create_merge_requests.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class CreateMergeRequests < ActiveRecord::Migration
- def change
- create_table :merge_requests do |t|
- t.string :target_branch, :null => false
- t.string :source_branch, :null => false
- t.integer :project_id, :null => false
- t.integer :author_id
- t.integer :assignee_id
- t.string :title
- t.boolean :closed, :default => false, :null => false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb b/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb
deleted file mode 100644
index e2695fdafa..0000000000
--- a/db/migrate/20111206213842_add_advanced_rights_to_team_member.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddAdvancedRightsToTeamMember < ActiveRecord::Migration
- def change
- add_column :users_projects, :repo_access, :integer, :default => 0, :null => false
- add_column :users_projects, :project_access, :integer, :default => 0, :null => false
- end
-end
diff --git a/db/migrate/20111206222316_migrate_to_new_rights.rb b/db/migrate/20111206222316_migrate_to_new_rights.rb
deleted file mode 100644
index 22e0c1ce99..0000000000
--- a/db/migrate/20111206222316_migrate_to_new_rights.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-class MigrateToNewRights < ActiveRecord::Migration
- def up
- # Repository access
- UsersProject.update_all("repo_access = 2", :write => true)
- UsersProject.update_all("repo_access = 1", :read => true, :write => false)
-
- # Project access
- UsersProject.update_all("project_access = 1", :read => true, :write => false, :admin => false)
- UsersProject.update_all("project_access = 2", :read => true, :write => true, :admin => false)
- UsersProject.update_all("project_access = 3", :read => true, :write => true, :admin => true)
-
- # Remove old fields
- remove_column :users_projects, :read
- remove_column :users_projects, :write
- remove_column :users_projects, :admin
- end
-
- def down
- end
-end
diff --git a/db/migrate/20111207211728_add_default_branch_to_project.rb b/db/migrate/20111207211728_add_default_branch_to_project.rb
deleted file mode 100644
index 5c2107f7aa..0000000000
--- a/db/migrate/20111207211728_add_default_branch_to_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddDefaultBranchToProject < ActiveRecord::Migration
- def change
- add_column :projects, :default_branch, :string, :null => false, :default => "master"
- end
-end
diff --git a/db/migrate/20111214091851_create_web_hooks.rb b/db/migrate/20111214091851_create_web_hooks.rb
deleted file mode 100644
index c6ba89c10e..0000000000
--- a/db/migrate/20111214091851_create_web_hooks.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class CreateWebHooks < ActiveRecord::Migration
- def change
- create_table :web_hooks do |t|
- t.string :url
- t.integer :project_id
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20111220190817_add_coloscheme_option_to_user.rb b/db/migrate/20111220190817_add_coloscheme_option_to_user.rb
deleted file mode 100644
index fe06c3aa34..0000000000
--- a/db/migrate/20111220190817_add_coloscheme_option_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddColoschemeOptionToUser < ActiveRecord::Migration
- def change
- add_column :users, :dark_scheme, :boolean, :default => false, :null => false
- end
-end
diff --git a/db/migrate/20111231111825_add_project_id_to_key.rb b/db/migrate/20111231111825_add_project_id_to_key.rb
deleted file mode 100644
index dc80cbdb71..0000000000
--- a/db/migrate/20111231111825_add_project_id_to_key.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddProjectIdToKey < ActiveRecord::Migration
- def change
- add_column :keys, :project_id, :integer, :null => true
- change_column :keys, :user_id, :integer, :null => true
- end
-end
diff --git a/db/migrate/20120110180749_add_line_number_to_note.rb b/db/migrate/20120110180749_add_line_number_to_note.rb
deleted file mode 100644
index 9bdcce3c47..0000000000
--- a/db/migrate/20120110180749_add_line_number_to_note.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddLineNumberToNote < ActiveRecord::Migration
- def change
- add_column :notes, :line_code, :string, :null => true
- end
-end
diff --git a/db/migrate/20120119202326_add_indexes.rb b/db/migrate/20120119202326_add_indexes.rb
deleted file mode 100644
index ee07176a26..0000000000
--- a/db/migrate/20120119202326_add_indexes.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class AddIndexes < ActiveRecord::Migration
- def change
- add_index :issues, :project_id
- add_index :merge_requests, :project_id
- add_index :notes, :noteable_id
- add_index :notes, :noteable_type
- end
-
-end
diff --git a/db/migrate/20120121122616_fix_noteable_id.rb b/db/migrate/20120121122616_fix_noteable_id.rb
deleted file mode 100644
index d110e8cd63..0000000000
--- a/db/migrate/20120121122616_fix_noteable_id.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class FixNoteableId < ActiveRecord::Migration
- def up
- change_column :notes, :noteable_id, :string, :limit => 255
- end
-
- def down
- end
-end
diff --git a/db/migrate/20120206170141_add_modularity_fields_to_project.rb b/db/migrate/20120206170141_add_modularity_fields_to_project.rb
deleted file mode 100644
index d63de0c2be..0000000000
--- a/db/migrate/20120206170141_add_modularity_fields_to_project.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AddModularityFieldsToProject < ActiveRecord::Migration
- def change
- add_column :projects, :issues_enabled, :boolean, :null => false, :default => true
- add_column :projects, :wall_enabled, :boolean, :null => false, :default => true
- add_column :projects, :merge_requests_enabled, :boolean, :null => false, :default => true
- end
-end
diff --git a/db/migrate/20120215182305_create_protected_branches.rb b/db/migrate/20120215182305_create_protected_branches.rb
deleted file mode 100644
index 841d08c33d..0000000000
--- a/db/migrate/20120215182305_create_protected_branches.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class CreateProtectedBranches < ActiveRecord::Migration
- def change
- create_table :protected_branches do |t|
- t.integer :project_id, :null => false
- t.string :name, :null => false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20120216085842_move_to_roles_permissions.rb b/db/migrate/20120216085842_move_to_roles_permissions.rb
deleted file mode 100644
index 36d02cf972..0000000000
--- a/db/migrate/20120216085842_move_to_roles_permissions.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-class MoveToRolesPermissions < ActiveRecord::Migration
- def up
- repo_n = 0
- repo_r = 1
- repo_rw = 2
- project_rwa = 3
-
-
- # Build masters and reset repo_access
- UsersProject.update_all({:project_access => UsersProject::MASTER, :repo_access => 99 }, ["project_access = ?", project_rwa])
-
- # Build other roles based on repo access
- UsersProject.update_all ["project_access = ?", UsersProject::DEVELOPER], ["repo_access = ?", repo_rw]
- UsersProject.update_all ["project_access = ?", UsersProject::REPORTER], ["repo_access = ?", repo_r]
- UsersProject.update_all ["project_access = ?", UsersProject::GUEST], ["repo_access = ?", repo_n]
-
- remove_column :users_projects, :repo_access
- end
-
- def down
- end
-end
diff --git a/db/migrate/20120216215008_create_wikis.rb b/db/migrate/20120216215008_create_wikis.rb
deleted file mode 100644
index 38947df389..0000000000
--- a/db/migrate/20120216215008_create_wikis.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateWikis < ActiveRecord::Migration
- def change
- create_table :wikis do |t|
- t.string :title
- t.text :content
- t.integer :project_id
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20120219130957_add_slug_to_wiki.rb b/db/migrate/20120219130957_add_slug_to_wiki.rb
deleted file mode 100644
index 5f2d5970a8..0000000000
--- a/db/migrate/20120219130957_add_slug_to_wiki.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddSlugToWiki < ActiveRecord::Migration
- def change
- add_column :wikis, :slug, :string
-
- end
-end
diff --git a/db/migrate/20120219140810_add_wiki_enabled_to_project.rb b/db/migrate/20120219140810_add_wiki_enabled_to_project.rb
deleted file mode 100644
index ebd71bea84..0000000000
--- a/db/migrate/20120219140810_add_wiki_enabled_to_project.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddWikiEnabledToProject < ActiveRecord::Migration
- def change
- add_column :projects, :wiki_enabled, :boolean, :default => true, :null => false
-
- end
-end
diff --git a/db/migrate/20120219193300_add_user_to_wiki.rb b/db/migrate/20120219193300_add_user_to_wiki.rb
deleted file mode 100644
index 8a6c0a06ef..0000000000
--- a/db/migrate/20120219193300_add_user_to_wiki.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddUserToWiki < ActiveRecord::Migration
- def change
- add_column :wikis, :user_id, :integer
-
- end
-end
diff --git a/db/migrate/20120228130210_create_events.rb b/db/migrate/20120228130210_create_events.rb
deleted file mode 100644
index c01f557aaa..0000000000
--- a/db/migrate/20120228130210_create_events.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class CreateEvents < ActiveRecord::Migration
- def change
- create_table :events do |t|
- t.string :target_type, :null => true
- t.integer :target_id, :null => true
-
- t.string :title, :null => true
- t.text :data, :null => true
- t.integer :project_id, :null => true
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20120228134252_add_action_to_event.rb b/db/migrate/20120228134252_add_action_to_event.rb
deleted file mode 100644
index aade3d90a8..0000000000
--- a/db/migrate/20120228134252_add_action_to_event.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddActionToEvent < ActiveRecord::Migration
- def change
- add_column :events, :action, :integer, :null => true
- end
-end
diff --git a/db/migrate/20120301185805_add_theme_to_user.rb b/db/migrate/20120301185805_add_theme_to_user.rb
deleted file mode 100644
index 7c2e55692b..0000000000
--- a/db/migrate/20120301185805_add_theme_to_user.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddThemeToUser < ActiveRecord::Migration
- def change
- add_column :users, :theme_id, :integer, :null => false, :default => 1
-
- end
-end
diff --git a/db/migrate/20120307095918_add_author_id_to_event.rb b/db/migrate/20120307095918_add_author_id_to_event.rb
deleted file mode 100644
index 1d24d41e42..0000000000
--- a/db/migrate/20120307095918_add_author_id_to_event.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddAuthorIdToEvent < ActiveRecord::Migration
- def change
- add_column :events, :author_id, :integer, :null => true
- end
-end
diff --git a/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb b/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb
deleted file mode 100644
index 2dc1dfb4b6..0000000000
--- a/db/migrate/20120315111711_add_commits_diff_store_to_merge_request.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddCommitsDiffStoreToMergeRequest < ActiveRecord::Migration
- def change
- add_column :merge_requests, :st_commits, :text, :null => true
- add_column :merge_requests, :st_diffs, :text, :null => true
- end
-end
diff --git a/db/migrate/20120315132931_add_merged_to_merge_request.rb b/db/migrate/20120315132931_add_merged_to_merge_request.rb
deleted file mode 100644
index 2deb59e442..0000000000
--- a/db/migrate/20120315132931_add_merged_to_merge_request.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddMergedToMergeRequest < ActiveRecord::Migration
- def change
- add_column :merge_requests, :merged, :boolean, :null => false, :default => false
- end
-end
diff --git a/db/migrate/20120317095543_add_description_to_issues.rb b/db/migrate/20120317095543_add_description_to_issues.rb
deleted file mode 100644
index e47e3f8e89..0000000000
--- a/db/migrate/20120317095543_add_description_to_issues.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddDescriptionToIssues < ActiveRecord::Migration
- def change
- add_column :issues, :description, :text
- end
-end
diff --git a/db/migrate/20120323221339_add_bio_field_to_user.rb b/db/migrate/20120323221339_add_bio_field_to_user.rb
deleted file mode 100644
index 80a4dec597..0000000000
--- a/db/migrate/20120323221339_add_bio_field_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddBioFieldToUser < ActiveRecord::Migration
- def change
- add_column :users, :bio, :string, :null => true
- end
-end
diff --git a/db/migrate/20120329170745_add_automerge_to_merge_request.rb b/db/migrate/20120329170745_add_automerge_to_merge_request.rb
deleted file mode 100644
index de7c68ee1c..0000000000
--- a/db/migrate/20120329170745_add_automerge_to_merge_request.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddAutomergeToMergeRequest < ActiveRecord::Migration
- def change
- add_column :merge_requests, :state, :integer, :null => false, :default => 1
- end
-end
diff --git a/db/migrate/20120405211750_increase_mr_text_column_size.rb b/db/migrate/20120405211750_increase_mr_text_column_size.rb
deleted file mode 100644
index 4fbef622da..0000000000
--- a/db/migrate/20120405211750_increase_mr_text_column_size.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class IncreaseMrTextColumnSize < ActiveRecord::Migration
- def up
- # MYSQL LARGETEXT for merge request
- change_column :merge_requests, :st_diffs, :text, :limit => 4294967295
- change_column :merge_requests, :st_commits, :text, :limit => 4294967295
- end
-
- def down
- end
-end
diff --git a/db/migrate/20120408180246_create_milestones.rb b/db/migrate/20120408180246_create_milestones.rb
deleted file mode 100644
index ed3a510d66..0000000000
--- a/db/migrate/20120408180246_create_milestones.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class CreateMilestones < ActiveRecord::Migration
- def change
- create_table :milestones do |t|
- t.string :title, :null => false
- t.integer :project_id, :null => false
- t.text :description
- t.date :due_date
- t.boolean :closed, :default => false, :null => false
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20120408181910_add_milestone_id_to_issue.rb b/db/migrate/20120408181910_add_milestone_id_to_issue.rb
deleted file mode 100644
index a6b44090c1..0000000000
--- a/db/migrate/20120408181910_add_milestone_id_to_issue.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddMilestoneIdToIssue < ActiveRecord::Migration
- def change
- add_column :issues, :milestone_id, :integer, :null => true
- end
-end
diff --git a/db/migrate/20120413135904_add_blocked_field_to_user.rb b/db/migrate/20120413135904_add_blocked_field_to_user.rb
deleted file mode 100644
index 050450bf03..0000000000
--- a/db/migrate/20120413135904_add_blocked_field_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddBlockedFieldToUser < ActiveRecord::Migration
- def change
- add_column :users, :blocked, :boolean, :null => false, :default => false
- end
-end
diff --git a/db/migrate/20120627145613_remove_critical_from_issue.rb b/db/migrate/20120627145613_remove_critical_from_issue.rb
deleted file mode 100644
index f8d0797197..0000000000
--- a/db/migrate/20120627145613_remove_critical_from_issue.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RemoveCriticalFromIssue < ActiveRecord::Migration
- def up
- remove_column :issues, :critical
- end
-
- def down
- add_column :issues, :critical, :boolean, :null => true, :default => false
- end
-end
diff --git a/db/migrate/20120706065612_add_lockable_to_users.rb b/db/migrate/20120706065612_add_lockable_to_users.rb
deleted file mode 100644
index cf86e66087..0000000000
--- a/db/migrate/20120706065612_add_lockable_to_users.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class AddLockableToUsers < ActiveRecord::Migration
- def change
- add_column :users, :failed_attempts, :integer, :default => 0
- add_column :users, :locked_at, :datetime
- end
-end
diff --git a/db/migrate/20120712080407_add_type_to_web_hook.rb b/db/migrate/20120712080407_add_type_to_web_hook.rb
deleted file mode 100644
index 18ab024c81..0000000000
--- a/db/migrate/20120712080407_add_type_to_web_hook.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddTypeToWebHook < ActiveRecord::Migration
- def change
- add_column :web_hooks, :type, :string, :default => "ProjectHook"
- end
-end
diff --git a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb
deleted file mode 100644
index d5e66ba4d3..0000000000
--- a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class AddExternAuthProviderToUsers < ActiveRecord::Migration
- def change
- add_column :users, :extern_uid, :string
- add_column :users, :provider, :string
-
- add_index :users, [:extern_uid, :provider], :unique => true
- end
-end
diff --git a/db/migrate/20120905043334_set_default_branch_default_to_nil.rb b/db/migrate/20120905043334_set_default_branch_default_to_nil.rb
deleted file mode 100644
index f5956fe875..0000000000
--- a/db/migrate/20120905043334_set_default_branch_default_to_nil.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class SetDefaultBranchDefaultToNil < ActiveRecord::Migration
- def up
- # Set the default_branch to allow nil, and default it to nil
- change_column_null(:projects, :default_branch, true)
- change_column_default(:projects, :default_branch, nil)
- end
-
- def down
- change_column_null(:projects, :default_branch, false)
- change_column_default(:projects, :default_branch, 'master')
- end
-end
diff --git a/db/migrate/20121002150926_create_groups.rb b/db/migrate/20121002150926_create_groups.rb
deleted file mode 100644
index ac178294c0..0000000000
--- a/db/migrate/20121002150926_create_groups.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateGroups < ActiveRecord::Migration
- def change
- create_table :groups do |t|
- t.string :name, null: false
- t.string :code, null: false
- t.integer :owner_id, null: false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20121002151033_add_group_id_to_project.rb b/db/migrate/20121002151033_add_group_id_to_project.rb
deleted file mode 100644
index 683fbfec51..0000000000
--- a/db/migrate/20121002151033_add_group_id_to_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddGroupIdToProject < ActiveRecord::Migration
- def change
- add_column :projects, :group_id, :integer
- end
-end
diff --git a/db/migrate/20121009205010_postgres_create_integer_cast.rb b/db/migrate/20121009205010_postgres_create_integer_cast.rb
deleted file mode 100644
index b9a971387d..0000000000
--- a/db/migrate/20121009205010_postgres_create_integer_cast.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class PostgresCreateIntegerCast < ActiveRecord::Migration
- def up
- execute <<-SQL
- CREATE CAST (integer AS text) WITH INOUT AS IMPLICIT;
- SQL
- rescue ActiveRecord::StatementInvalid
- end
-
- def down
- execute <<-SQL
- DROP CAST (integer AS text);
- SQL
- rescue ActiveRecord::StatementInvalid
- end
-end
diff --git a/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb b/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb
deleted file mode 100644
index b516772434..0000000000
--- a/db/migrate/20121026114600_add_milestone_id_to_merge_requests.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddMilestoneIdToMergeRequests < ActiveRecord::Migration
- def change
- add_column :merge_requests, :milestone_id, :integer, :null => true
- end
-end
diff --git a/db/migrate/20121119170638_create_services.rb b/db/migrate/20121119170638_create_services.rb
deleted file mode 100644
index f7cac1445a..0000000000
--- a/db/migrate/20121119170638_create_services.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class CreateServices < ActiveRecord::Migration
- def change
- create_table :services do |t|
- t.string :type
- t.string :title
- t.string :token
- t.integer :project_id, null: false
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20121120051432_add_service_id_to_web_hook.rb b/db/migrate/20121120051432_add_service_id_to_web_hook.rb
deleted file mode 100644
index 77adf92578..0000000000
--- a/db/migrate/20121120051432_add_service_id_to_web_hook.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddServiceIdToWebHook < ActiveRecord::Migration
- def change
- add_column :web_hooks, :service_id, :integer, null: true
- end
-end
diff --git a/db/migrate/20121120103700_add_active_to_service.rb b/db/migrate/20121120103700_add_active_to_service.rb
deleted file mode 100644
index f45ef52e6f..0000000000
--- a/db/migrate/20121120103700_add_active_to_service.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddActiveToService < ActiveRecord::Migration
- def change
- add_column :services, :active, :boolean, default: false, null: false
- end
-end
diff --git a/db/migrate/20121120113838_add_project_url_to_service.rb b/db/migrate/20121120113838_add_project_url_to_service.rb
deleted file mode 100644
index 13ffbdb192..0000000000
--- a/db/migrate/20121120113838_add_project_url_to_service.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddProjectUrlToService < ActiveRecord::Migration
- def change
- add_column :services, :project_url, :string, null: true
- end
-end
diff --git a/db/migrate/20121122145155_convert_group_to_namespace.rb b/db/migrate/20121122145155_convert_group_to_namespace.rb
deleted file mode 100644
index fc8b023d81..0000000000
--- a/db/migrate/20121122145155_convert_group_to_namespace.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 904f3aa32b..0000000000
--- a/db/migrate/20121122150932_add_namespace_id_to_project.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index 04232a119d..0000000000
--- a/db/migrate/20121123104937_add_username_to_user.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index fb10baf58c..0000000000
--- a/db/migrate/20121123164910_rename_code_to_path.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-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
deleted file mode 100644
index 502a2ccbcd..0000000000
--- a/db/migrate/20121203154450_add_events_indices.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 52170a7c59..0000000000
--- a/db/migrate/20121203160507_more_indices.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-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
deleted file mode 100644
index a2b36f7fdf..0000000000
--- a/db/migrate/20121205201726_add_more_indexes.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-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
deleted file mode 100644
index 6f2da4136a..0000000000
--- a/db/migrate/20121218164840_move_noteable_commit_to_own_field.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100644
index 4c5d041ce8..0000000000
--- a/db/migrate/20121219095402_indices_for_notes.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class IndicesForNotes < ActiveRecord::Migration
- def change
- add_index :notes, :commit_id
- add_index :notes, [:project_id, :noteable_type]
- end
-end
diff --git a/db/migrate/20121219183753_create_user_teams.rb b/db/migrate/20121219183753_create_user_teams.rb
deleted file mode 100644
index 65c4d05398..0000000000
--- a/db/migrate/20121219183753_create_user_teams.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateUserTeams < ActiveRecord::Migration
- def change
- create_table :user_teams do |t|
- t.string :name
- t.string :path
- t.integer :owner_id
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20121220064104_create_user_team_project_relationships.rb b/db/migrate/20121220064104_create_user_team_project_relationships.rb
deleted file mode 100644
index 8eb654c872..0000000000
--- a/db/migrate/20121220064104_create_user_team_project_relationships.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateUserTeamProjectRelationships < ActiveRecord::Migration
- def change
- create_table :user_team_project_relationships do |t|
- t.integer :project_id
- t.integer :user_team_id
- t.integer :greatest_access
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20121220064453_create_user_team_user_relationships.rb b/db/migrate/20121220064453_create_user_team_user_relationships.rb
deleted file mode 100644
index 7783b0ae43..0000000000
--- a/db/migrate/20121220064453_create_user_team_user_relationships.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class CreateUserTeamUserRelationships < ActiveRecord::Migration
- def change
- create_table :user_team_user_relationships do |t|
- t.integer :user_id
- t.integer :user_team_id
- t.boolean :group_admin
- t.integer :permission
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20121220064453_init_schema.rb b/db/migrate/20121220064453_init_schema.rb
new file mode 100644
index 0000000000..90f5eb08e8
--- /dev/null
+++ b/db/migrate/20121220064453_init_schema.rb
@@ -0,0 +1,306 @@
+class InitSchema < ActiveRecord::Migration
+ def up
+
+ create_table "events", force: true do |t|
+ t.string "target_type"
+ t.integer "target_id"
+ t.string "title"
+ t.text "data"
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "action"
+ t.integer "author_id"
+ end
+
+ add_index "events", ["action"], name: "index_events_on_action", using: :btree
+ add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
+ add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree
+ add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree
+ add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
+ add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
+
+ create_table "issues", force: true do |t|
+ t.string "title"
+ t.integer "assignee_id"
+ t.integer "author_id"
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "closed", default: false, null: false
+ t.integer "position", default: 0
+ t.string "branch_name"
+ t.text "description"
+ t.integer "milestone_id"
+ end
+
+ add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
+ add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
+ add_index "issues", ["closed"], name: "index_issues_on_closed", using: :btree
+ add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
+ add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
+ add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
+ add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
+
+ create_table "keys", force: true do |t|
+ t.integer "user_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.text "key"
+ t.string "title"
+ t.string "identifier"
+ t.integer "project_id"
+ end
+
+ add_index "keys", ["identifier"], name: "index_keys_on_identifier", using: :btree
+ add_index "keys", ["project_id"], name: "index_keys_on_project_id", using: :btree
+ add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
+
+ create_table "merge_requests", force: true do |t|
+ t.string "target_branch", null: false
+ t.string "source_branch", null: false
+ t.integer "project_id", null: false
+ t.integer "author_id"
+ t.integer "assignee_id"
+ t.string "title"
+ t.boolean "closed", default: false, null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.text "st_commits", limit: 2147483647
+ t.text "st_diffs", limit: 2147483647
+ t.boolean "merged", default: false, null: false
+ t.integer "state", default: 1, null: false
+ t.integer "milestone_id"
+ end
+
+ add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
+ add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
+ add_index "merge_requests", ["closed"], name: "index_merge_requests_on_closed", using: :btree
+ add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
+ add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
+ add_index "merge_requests", ["project_id"], name: "index_merge_requests_on_project_id", using: :btree
+ add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
+ add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
+ add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
+
+ create_table "milestones", force: true do |t|
+ t.string "title", null: false
+ t.integer "project_id", null: false
+ t.text "description"
+ t.date "due_date"
+ t.boolean "closed", default: false, null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
+ add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
+
+ 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"
+ t.datetime "updated_at"
+ t.string "type"
+ end
+
+ add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
+ add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
+ add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
+ add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
+
+ create_table "notes", force: true do |t|
+ t.text "note"
+ t.string "noteable_type"
+ t.integer "author_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "project_id"
+ t.string "attachment"
+ t.string "line_code"
+ t.string "commit_id"
+ t.integer "noteable_id"
+ end
+
+ add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
+ add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
+ add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
+ add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
+ add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
+
+ create_table "projects", force: true do |t|
+ t.string "name"
+ t.string "path"
+ t.text "description"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "private_flag", default: true, null: false
+ 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 "namespace_id"
+ end
+
+ add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
+ add_index "projects", ["owner_id"], name: "index_projects_on_owner_id", using: :btree
+
+ create_table "protected_branches", force: true do |t|
+ t.integer "project_id", null: false
+ t.string "name", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "services", force: true do |t|
+ t.string "type"
+ t.string "title"
+ t.string "token"
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "active", default: false, null: false
+ t.string "project_url"
+ end
+
+ add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
+
+ create_table "snippets", force: true do |t|
+ t.string "title"
+ t.text "content"
+ t.integer "author_id", null: false
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "file_name"
+ t.datetime "expires_at"
+ end
+
+ add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
+ add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
+ add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
+
+ create_table "taggings", force: true do |t|
+ t.integer "tag_id"
+ t.integer "taggable_id"
+ t.string "taggable_type"
+ t.integer "tagger_id"
+ t.string "tagger_type"
+ t.string "context"
+ t.datetime "created_at"
+ end
+
+ add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
+
+ create_table "tags", force: true do |t|
+ t.string "name"
+ end
+
+ create_table "user_team_project_relationships", force: true do |t|
+ t.integer "project_id"
+ t.integer "user_team_id"
+ t.integer "greatest_access"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "user_team_user_relationships", force: true do |t|
+ t.integer "user_id"
+ t.integer "user_team_id"
+ t.boolean "group_admin"
+ t.integer "permission"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "user_teams", force: true do |t|
+ t.string "name"
+ t.string "path"
+ t.integer "owner_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "users", force: true do |t|
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
+ t.string "reset_password_token"
+ t.datetime "reset_password_sent_at"
+ t.datetime "remember_created_at"
+ t.integer "sign_in_count", default: 0
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "name"
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
+ t.string "authentication_token"
+ t.boolean "dark_scheme", default: false, null: false
+ t.integer "theme_id", default: 1, null: false
+ t.string "bio"
+ t.boolean "blocked", default: false, null: false
+ t.integer "failed_attempts", default: 0
+ t.datetime "locked_at"
+ t.string "extern_uid"
+ t.string "provider"
+ t.string "username"
+ end
+
+ add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
+ add_index "users", ["blocked"], name: "index_users_on_blocked", using: :btree
+ add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+ add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
+ add_index "users", ["name"], name: "index_users_on_name", using: :btree
+ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
+ add_index "users", ["username"], name: "index_users_on_username", using: :btree
+
+ create_table "users_projects", force: true do |t|
+ t.integer "user_id", null: false
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "project_access", default: 0, null: false
+ end
+
+ add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree
+ add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree
+ add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree
+
+ create_table "web_hooks", force: true do |t|
+ t.string "url"
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "type", default: "ProjectHook"
+ t.integer "service_id"
+ end
+
+ create_table "wikis", force: true do |t|
+ t.string "title"
+ t.text "content"
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "slug"
+ t.integer "user_id"
+ end
+
+ add_index "wikis", ["project_id"], name: "index_wikis_on_project_id", using: :btree
+ add_index "wikis", ["slug"], name: "index_wikis_on_slug", using: :btree
+
+ end
+
+ def down
+ raise "Can not revert initial migration"
+ end
+end
diff --git a/db/migrate/20130506095501_remove_project_id_from_key.rb b/db/migrate/20130506095501_remove_project_id_from_key.rb
index 4214fd45d1..6b794cfb5c 100644
--- a/db/migrate/20130506095501_remove_project_id_from_key.rb
+++ b/db/migrate/20130506095501_remove_project_id_from_key.rb
@@ -4,7 +4,7 @@ class RemoveProjectIdFromKey < ActiveRecord::Migration
Key.where('project_id IS NOT NULL').update_all(type: 'DeployKey')
DeployKey.all.each do |key|
- project = Project.find_by_id(key.project_id)
+ project = Project.find_by(id: key.project_id)
if project
project.deploy_keys << key
print '.'
diff --git a/db/migrate/20130809124851_add_permission_check_to_user.rb b/db/migrate/20130809124851_add_permission_check_to_user.rb
new file mode 100644
index 0000000000..c26157904c
--- /dev/null
+++ b/db/migrate/20130809124851_add_permission_check_to_user.rb
@@ -0,0 +1,5 @@
+class AddPermissionCheckToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :last_credential_check_at, :datetime
+ end
+end
diff --git a/db/migrate/20131106151520_remove_default_branch.rb b/db/migrate/20131106151520_remove_default_branch.rb
new file mode 100644
index 0000000000..88a890eb3e
--- /dev/null
+++ b/db/migrate/20131106151520_remove_default_branch.rb
@@ -0,0 +1,9 @@
+class RemoveDefaultBranch < ActiveRecord::Migration
+ def up
+ remove_column :projects, :default_branch
+ end
+
+ def down
+ add_column :projects, :default_branch, :string
+ end
+end
diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb
new file mode 100644
index 0000000000..147178e9dc
--- /dev/null
+++ b/db/migrate/20131112114325_create_broadcast_messages.rb
@@ -0,0 +1,12 @@
+class CreateBroadcastMessages < ActiveRecord::Migration
+ def change
+ create_table :broadcast_messages do |t|
+ t.text :message, null: false
+ t.datetime :starts_at
+ t.datetime :ends_at
+ t.integer :alert_type
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
new file mode 100644
index 0000000000..cf1e9f912a
--- /dev/null
+++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
@@ -0,0 +1,13 @@
+class AddVisibilityLevelToProjects < ActiveRecord::Migration
+ def self.up
+ add_column :projects, :visibility_level, :integer, :default => 0, :null => false
+ Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ remove_column :projects, :public
+ end
+
+ def self.down
+ add_column :projects, :public, :boolean, :default => false, :null => false
+ Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true)
+ remove_column :projects, :visibility_level
+ end
+end
diff --git a/db/migrate/20131129154016_add_archived_to_projects.rb b/db/migrate/20131129154016_add_archived_to_projects.rb
new file mode 100644
index 0000000000..917e690ba4
--- /dev/null
+++ b/db/migrate/20131129154016_add_archived_to_projects.rb
@@ -0,0 +1,5 @@
+class AddArchivedToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :archived, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb
new file mode 100644
index 0000000000..473f355ece
--- /dev/null
+++ b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb
@@ -0,0 +1,6 @@
+class AddColorAndFontToBroadcastMessages < ActiveRecord::Migration
+ def change
+ add_column :broadcast_messages, :color, :string
+ add_column :broadcast_messages, :font, :string
+ end
+end
diff --git a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb
new file mode 100644
index 0000000000..d29e996852
--- /dev/null
+++ b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb
@@ -0,0 +1,7 @@
+class AddEventFieldsForWebHook < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :push_events, :boolean, default: true, null: false
+ add_column :web_hooks, :issues_events, :boolean, default: false, null: false
+ add_column :web_hooks, :merge_requests_events, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb
new file mode 100644
index 0000000000..7cec79e7ee
--- /dev/null
+++ b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb
@@ -0,0 +1,5 @@
+class AddHideNoSshKeyToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :hide_no_ssh_key, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20131217102743_add_recipients_to_service.rb b/db/migrate/20131217102743_add_recipients_to_service.rb
new file mode 100644
index 0000000000..9695c25135
--- /dev/null
+++ b/db/migrate/20131217102743_add_recipients_to_service.rb
@@ -0,0 +1,5 @@
+class AddRecipientsToService < ActiveRecord::Migration
+ def change
+ add_column :services, :recipients, :text
+ end
+end
diff --git a/db/migrate/20140116231608_add_website_url_to_users.rb b/db/migrate/20140116231608_add_website_url_to_users.rb
new file mode 100644
index 0000000000..0996fdcad7
--- /dev/null
+++ b/db/migrate/20140116231608_add_website_url_to_users.rb
@@ -0,0 +1,5 @@
+class AddWebsiteUrlToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :website_url, :string, {:null => false, :default => ''}
+ end
+end
diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb
new file mode 100644
index 0000000000..ef592305a2
--- /dev/null
+++ b/db/migrate/20140122112253_create_merge_request_diffs.rb
@@ -0,0 +1,12 @@
+class CreateMergeRequestDiffs < ActiveRecord::Migration
+ def change
+ create_table :merge_request_diffs do |t|
+ t.string :state, null: false, default: 'collected'
+ t.text :st_commits, null: true, limit: 2147483647
+ t.text :st_diffs, null: true, limit: 2147483647
+ t.integer :merge_request_id, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20140122114406_migrate_mr_diffs.rb b/db/migrate/20140122114406_migrate_mr_diffs.rb
new file mode 100644
index 0000000000..1595e2b647
--- /dev/null
+++ b/db/migrate/20140122114406_migrate_mr_diffs.rb
@@ -0,0 +1,9 @@
+class MigrateMrDiffs < ActiveRecord::Migration
+ def self.up
+ execute "INSERT INTO merge_request_diffs ( merge_request_id, st_commits, st_diffs ) SELECT id, st_commits, st_diffs FROM merge_requests"
+ end
+
+ def self.down
+ MergeRequestDiff.delete_all
+ end
+end
diff --git a/db/migrate/20140122122549_remove_m_rdiff_fields.rb b/db/migrate/20140122122549_remove_m_rdiff_fields.rb
new file mode 100644
index 0000000000..8f863d85a6
--- /dev/null
+++ b/db/migrate/20140122122549_remove_m_rdiff_fields.rb
@@ -0,0 +1,21 @@
+class RemoveMRdiffFields < ActiveRecord::Migration
+ def up
+ remove_column :merge_requests, :st_commits
+ remove_column :merge_requests, :st_diffs
+ end
+
+ def down
+ add_column :merge_requests, :st_commits, :text, null: true, limit: 2147483647
+ add_column :merge_requests, :st_diffs, :text, null: true, limit: 2147483647
+
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
+ execute "UPDATE merge_requests mr
+ SET (st_commits, st_diffs) = (md.st_commits, md.st_diffs)
+ FROM merge_request_diffs md
+ WHERE md.merge_request_id = mr.id"
+ else
+ execute "UPDATE merge_requests mr, merge_request_diffs md SET mr.st_commits = md.st_commits WHERE md.merge_request_id = mr.id"
+ execute "UPDATE merge_requests mr, merge_request_diffs md SET mr.st_diffs = md.st_diffs WHERE md.merge_request_id = mr.id"
+ end
+ end
+end
diff --git a/db/migrate/20140127170938_add_group_avatars.rb b/db/migrate/20140127170938_add_group_avatars.rb
new file mode 100644
index 0000000000..2911096dd5
--- /dev/null
+++ b/db/migrate/20140127170938_add_group_avatars.rb
@@ -0,0 +1,5 @@
+class AddGroupAvatars < ActiveRecord::Migration
+ def change
+ add_column :namespaces, :avatar, :string
+ end
+end
diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb
new file mode 100644
index 0000000000..cb78c4af11
--- /dev/null
+++ b/db/migrate/20140209025651_create_emails.rb
@@ -0,0 +1,13 @@
+class CreateEmails < ActiveRecord::Migration
+ def change
+ create_table :emails do |t|
+ t.integer :user_id, null: false
+ t.string :email, null: false
+
+ t.timestamps
+ end
+
+ add_index :emails, :user_id
+ add_index :emails, :email, unique: true
+ end
+end
diff --git a/db/migrate/20140214102325_add_api_key_to_services.rb b/db/migrate/20140214102325_add_api_key_to_services.rb
new file mode 100644
index 0000000000..30eeca2c1f
--- /dev/null
+++ b/db/migrate/20140214102325_add_api_key_to_services.rb
@@ -0,0 +1,5 @@
+class AddApiKeyToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :api_key, :string
+ end
+end
diff --git a/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
new file mode 100644
index 0000000000..65d28e8cb0
--- /dev/null
+++ b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
@@ -0,0 +1,5 @@
+class AddIndexMergeRequestDiffsOnMergeRequestId < ActiveRecord::Migration
+ def change
+ add_index :merge_request_diffs, :merge_request_id, unique: true
+ end
+end
diff --git a/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
new file mode 100644
index 0000000000..7017148702
--- /dev/null
+++ b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
@@ -0,0 +1,5 @@
+class AddTagPushHooksToProjectHook < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :tag_push_events, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20140312145357_add_import_status_to_project.rb b/db/migrate/20140312145357_add_import_status_to_project.rb
new file mode 100644
index 0000000000..ef972e8342
--- /dev/null
+++ b/db/migrate/20140312145357_add_import_status_to_project.rb
@@ -0,0 +1,5 @@
+class AddImportStatusToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :import_status, :string
+ end
+end
diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb
new file mode 100644
index 0000000000..f4392c0f05
--- /dev/null
+++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb
@@ -0,0 +1,12 @@
+class MigrateAlreadyImportedProjects < ActiveRecord::Migration
+ def up
+ Project.where(imported: true).update_all(import_status: "finished")
+ Project.where(imported: false).update_all(import_status: "none")
+ remove_column :projects, :imported
+ end
+
+ def down
+ add_column :projects, :imported, :boolean, default: false
+ Project.where(import_status: 'finished').update_all(imported: true)
+ end
+end
diff --git a/db/migrate/20140407135544_fix_namespaces.rb b/db/migrate/20140407135544_fix_namespaces.rb
new file mode 100644
index 0000000000..8c4f2b0f6b
--- /dev/null
+++ b/db/migrate/20140407135544_fix_namespaces.rb
@@ -0,0 +1,10 @@
+class FixNamespaces < ActiveRecord::Migration
+ def up
+ Namespace.where('name <> path and type is null').each do |namespace|
+ namespace.update_attribute(:name, namespace.path)
+ end
+ end
+
+ def down
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
new file mode 100644
index 0000000000..1f6d85d5f6
--- /dev/null
+++ b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
@@ -0,0 +1,11 @@
+class ChangeStateToAllowEmptyMergeRequestDiffs < ActiveRecord::Migration
+ def up
+ change_column :merge_request_diffs, :state, :string, null: true,
+ default: nil
+ end
+
+ def down
+ change_column :merge_request_diffs, :state, :string, null: false,
+ default: 'collected'
+ end
+end
diff --git a/db/migrate/20140415124820_limits_to_mysql.rb b/db/migrate/20140415124820_limits_to_mysql.rb
new file mode 100644
index 0000000000..3f6e62617c
--- /dev/null
+++ b/db/migrate/20140415124820_limits_to_mysql.rb
@@ -0,0 +1 @@
+require_relative 'limits_to_mysql'
diff --git a/db/migrate/20140416074002_add_index_on_iid.rb b/db/migrate/20140416074002_add_index_on_iid.rb
new file mode 100644
index 0000000000..85269e2a03
--- /dev/null
+++ b/db/migrate/20140416074002_add_index_on_iid.rb
@@ -0,0 +1,32 @@
+class AddIndexOnIid < ActiveRecord::Migration
+ def change
+ RemoveDuplicateIid.clean(Issue)
+ RemoveDuplicateIid.clean(MergeRequest, 'target_project_id')
+ RemoveDuplicateIid.clean(Milestone)
+
+ add_index :issues, [:project_id, :iid], unique: true
+ add_index :merge_requests, [:target_project_id, :iid], unique: true
+ add_index :milestones, [:project_id, :iid], unique: true
+ end
+end
+
+class RemoveDuplicateIid
+ def self.clean(klass, project_field = 'project_id')
+ duplicates = klass.find_by_sql("SELECT iid, #{project_field} FROM #{klass.table_name} GROUP BY #{project_field}, iid HAVING COUNT(*) > 1")
+
+ duplicates.each do |duplicate|
+ project_id = duplicate.send(project_field)
+ iid = duplicate.iid
+ items = klass.of_projects(project_id).where(iid: iid)
+
+ if items.size > 1
+ puts "Remove #{klass.name} duplicates for iid: #{iid} and project_id: #{project_id}"
+ items.shift
+ items.each do |item|
+ item.destroy
+ puts '.'
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20140416185734_index_on_current_sign_in_at.rb b/db/migrate/20140416185734_index_on_current_sign_in_at.rb
new file mode 100644
index 0000000000..0bf80ce154
--- /dev/null
+++ b/db/migrate/20140416185734_index_on_current_sign_in_at.rb
@@ -0,0 +1,5 @@
+class IndexOnCurrentSignInAt < ActiveRecord::Migration
+ def change
+ add_index :users, :current_sign_in_at
+ end
+end
diff --git a/db/migrate/20140428105831_add_notes_index_updated_at.rb b/db/migrate/20140428105831_add_notes_index_updated_at.rb
new file mode 100644
index 0000000000..6c25570f12
--- /dev/null
+++ b/db/migrate/20140428105831_add_notes_index_updated_at.rb
@@ -0,0 +1,5 @@
+class AddNotesIndexUpdatedAt < ActiveRecord::Migration
+ def change
+ add_index :notes, :updated_at
+ end
+end
diff --git a/db/migrate/20140502115131_add_repo_size_to_db.rb b/db/migrate/20140502115131_add_repo_size_to_db.rb
new file mode 100644
index 0000000000..7361d1a944
--- /dev/null
+++ b/db/migrate/20140502115131_add_repo_size_to_db.rb
@@ -0,0 +1,5 @@
+class AddRepoSizeToDb < ActiveRecord::Migration
+ def change
+ add_column :projects, :repository_size, :float, default: 0
+ end
+end
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
new file mode 100644
index 0000000000..eed6d36681
--- /dev/null
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -0,0 +1,21 @@
+class MigrateRepoSize < ActiveRecord::Migration
+ def up
+ Project.reset_column_information
+ Project.find_each(batch_size: 500) do |project|
+ begin
+ if project.empty_repo?
+ print '-'
+ else
+ project.update_repository_size
+ print '.'
+ end
+ rescue
+ print 'F'
+ end
+ end
+ puts 'Done'
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
new file mode 100644
index 0000000000..2b7afae6d7
--- /dev/null
+++ b/db/migrate/limits_to_mysql.rb
@@ -0,0 +1,10 @@
+class LimitsToMysql < ActiveRecord::Migration
+ def up
+ return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
+
+ change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647
+ change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
+ change_column :snippets, :content, :text, limit: 2147483647
+ change_column :notes, :st_diff, :text, limit: 2147483647
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d6acb2f90e..93837337af 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -9,55 +9,79 @@
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
-# It's strongly recommended to check this file into your version control system.
+# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20131009115346) do
+ActiveRecord::Schema.define(version: 20140502125220) do
- create_table "deploy_keys_projects", :force => true do |t|
- t.integer "deploy_key_id", :null => false
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
+
+ create_table "broadcast_messages", force: true do |t|
+ t.text "message", null: false
+ t.datetime "starts_at"
+ t.datetime "ends_at"
+ t.integer "alert_type"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "color"
+ t.string "font"
end
- add_index "deploy_keys_projects", ["project_id"], :name => "index_deploy_keys_projects_on_project_id"
+ create_table "deploy_keys_projects", force: true do |t|
+ t.integer "deploy_key_id", null: false
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
- create_table "events", :force => true do |t|
+ add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
+
+ create_table "emails", force: true do |t|
+ t.integer "user_id", null: false
+ t.string "email", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
+ add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
+
+ create_table "events", force: true do |t|
t.string "target_type"
t.integer "target_id"
t.string "title"
t.text "data"
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "action"
t.integer "author_id"
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"
+ add_index "events", ["action"], name: "index_events_on_action", using: :btree
+ add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
+ add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree
+ add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree
+ add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
+ add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
- create_table "forked_project_links", :force => true do |t|
- t.integer "forked_to_project_id", :null => false
- t.integer "forked_from_project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ create_table "forked_project_links", force: true do |t|
+ t.integer "forked_to_project_id", null: false
+ t.integer "forked_from_project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
- add_index "forked_project_links", ["forked_to_project_id"], :name => "index_forked_project_links_on_forked_to_project_id", :unique => true
+ add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
- create_table "issues", :force => true do |t|
+ create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
t.integer "author_id"
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "position", :default => 0
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "position", default: 0
t.string "branch_name"
t.text "description"
t.integer "milestone_id"
@@ -65,174 +89,191 @@ ActiveRecord::Schema.define(:version => 20131009115346) do
t.integer "iid"
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", ["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"
+ add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
+ add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
+ add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
+ add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
+ add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
+ add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
+ add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
- create_table "keys", :force => true do |t|
+ create_table "keys", force: true do |t|
t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.text "key"
t.string "title"
t.string "type"
t.string "fingerprint"
end
- add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
+ add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
- create_table "merge_requests", :force => true do |t|
- t.string "target_branch", :null => false
- t.string "source_branch", :null => false
- t.integer "source_project_id", :null => false
+ create_table "merge_request_diffs", force: true do |t|
+ t.string "state"
+ t.text "st_commits"
+ t.text "st_diffs"
+ t.integer "merge_request_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
+
+ create_table "merge_requests", force: true do |t|
+ t.string "target_branch", null: false
+ t.string "source_branch", null: false
+ t.integer "source_project_id", null: false
t.integer "author_id"
t.integer "assignee_id"
t.string "title"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.text "st_commits", :limit => 2147483647
- t.text "st_diffs", :limit => 2147483647
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "milestone_id"
t.string "state"
t.string "merge_status"
- t.integer "target_project_id", :null => false
+ t.integer "target_project_id", null: false
t.integer "iid"
t.text "description"
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", ["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", ["source_branch"], :name => "index_merge_requests_on_source_branch"
- add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id"
- add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch"
- add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
+ add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
+ add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
+ add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
+ add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
+ add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
+ add_index "merge_requests", ["source_project_id"], name: "index_merge_requests_on_source_project_id", using: :btree
+ add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
+ add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
+ add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
- create_table "milestones", :force => true do |t|
- t.string "title", :null => false
- t.integer "project_id", :null => false
+ create_table "milestones", force: true do |t|
+ t.string "title", null: false
+ t.integer "project_id", null: false
t.text "description"
t.date "due_date"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "state"
t.integer "iid"
end
- add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
- add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id"
+ add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
+ add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
+ add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
- create_table "namespaces", :force => true do |t|
- t.string "name", :null => false
- t.string "path", :null => false
+ create_table "namespaces", force: true do |t|
+ t.string "name", null: false
+ t.string "path", null: false
t.integer "owner_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "type"
- t.string "description", :default => "", :null => false
+ t.string "description", default: "", null: false
+ t.string "avatar"
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"
+ add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
+ add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
+ add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
+ add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
- create_table "notes", :force => true do |t|
+ create_table "notes", force: true do |t|
t.text "note"
t.string "noteable_type"
t.integer "author_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "project_id"
t.string "attachment"
t.string "line_code"
t.string "commit_id"
t.integer "noteable_id"
+ t.boolean "system", default: false, null: false
t.text "st_diff"
- t.boolean "system", :default => false, :null => false
end
- add_index "notes", ["author_id"], :name => "index_notes_on_author_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_id", "noteable_type"], :name => "index_notes_on_noteable_id_and_noteable_type"
- 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"
+ add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
+ add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
+ add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
+ add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
+ add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
+ add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
+ add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
+ add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
- create_table "projects", :force => true do |t|
+ create_table "projects", force: true do |t|
t.string "name"
t.string "path"
t.text "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.integer "creator_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.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 "namespace_id"
- t.boolean "public", :default => false, :null => false
- t.string "issues_tracker", :default => "gitlab", :null => false
+ t.string "issues_tracker", default: "gitlab", null: false
t.string "issues_tracker_id"
- t.boolean "snippets_enabled", :default => true, :null => false
+ t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at"
- t.boolean "imported", :default => false, :null => false
t.string "import_url"
+ t.integer "visibility_level", default: 0, null: false
+ t.boolean "archived", default: false, null: false
+ t.string "import_status"
+ t.float "repository_size", default: 0.0
end
- add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
- add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at"
- add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id"
+ add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
+ add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
+ add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
- create_table "protected_branches", :force => true do |t|
- t.integer "project_id", :null => false
- t.string "name", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ create_table "protected_branches", force: true do |t|
+ t.integer "project_id", null: false
+ t.string "name", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
- add_index "protected_branches", ["project_id"], :name => "index_protected_branches_on_project_id"
+ add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
- create_table "services", :force => true do |t|
+ create_table "services", force: true do |t|
t.string "type"
t.string "title"
t.string "token"
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.boolean "active", :default => false, :null => false
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "active", default: false, null: false
t.string "project_url"
t.string "subdomain"
t.string "room"
+ t.text "recipients"
+ t.string "api_key"
end
- add_index "services", ["project_id"], :name => "index_services_on_project_id"
+ add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
- create_table "snippets", :force => true do |t|
+ create_table "snippets", force: true do |t|
t.string "title"
- t.text "content", :limit => 2147483647
- t.integer "author_id", :null => false
+ t.text "content"
+ t.integer "author_id", null: false
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "file_name"
t.datetime "expires_at"
- t.boolean "private", :default => true, :null => false
+ t.boolean "private", default: true, null: false
t.string "type"
end
- add_index "snippets", ["author_id"], :name => "index_snippets_on_author_id"
- 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"
+ add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
+ add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
+ add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
+ add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
- create_table "taggings", :force => true do |t|
+ create_table "taggings", force: true do |t|
t.integer "tag_id"
t.integer "taggable_id"
t.string "taggable_type"
@@ -242,45 +283,45 @@ ActiveRecord::Schema.define(:version => 20131009115346) do
t.datetime "created_at"
end
- add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
- add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
+ add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
- create_table "tags", :force => true do |t|
+ create_table "tags", force: true do |t|
t.string "name"
end
- create_table "users", :force => true do |t|
- t.string "email", :default => "", :null => false
- t.string "encrypted_password", :default => "", :null => false
+ create_table "users", force: true do |t|
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", :default => 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
t.string "name"
- t.boolean "admin", :default => false, :null => false
- t.integer "projects_limit", :default => 10
- t.string "skype", :default => "", :null => false
- t.string "linkedin", :default => "", :null => false
- t.string "twitter", :default => "", :null => false
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", :default => 1, :null => false
+ t.integer "theme_id", default: 1, null: false
t.string "bio"
- t.integer "failed_attempts", :default => 0
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
t.string "username"
- t.boolean "can_create_group", :default => true, :null => false
- t.boolean "can_create_team", :default => true, :null => false
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
t.string "state"
- t.integer "color_scheme_id", :default => 1, :null => false
- t.integer "notification_level", :default => 1, :null => false
+ t.integer "color_scheme_id", default: 1, null: false
+ t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.string "avatar"
@@ -288,50 +329,58 @@ ActiveRecord::Schema.define(:version => 20131009115346) do
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", default: "", null: false
+ t.datetime "last_credential_check_at"
end
- add_index "users", ["admin"], :name => "index_users_on_admin"
- add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
- add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
- 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"
+ add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
+ add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
+ add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
+ add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
+ add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+ add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
+ add_index "users", ["name"], name: "index_users_on_name", using: :btree
+ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
+ add_index "users", ["username"], name: "index_users_on_username", using: :btree
- create_table "users_groups", :force => true do |t|
- t.integer "group_access", :null => false
- t.integer "group_id", :null => false
- t.integer "user_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "notification_level", :default => 3, :null => false
+ create_table "users_groups", force: true do |t|
+ t.integer "group_access", null: false
+ t.integer "group_id", null: false
+ t.integer "user_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "notification_level", default: 3, null: false
end
- add_index "users_groups", ["user_id"], :name => "index_users_groups_on_user_id"
+ add_index "users_groups", ["user_id"], name: "index_users_groups_on_user_id", using: :btree
- create_table "users_projects", :force => true do |t|
- t.integer "user_id", :null => false
- t.integer "project_id", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.integer "project_access", :default => 0, :null => false
- t.integer "notification_level", :default => 3, :null => false
+ create_table "users_projects", force: true do |t|
+ t.integer "user_id", null: false
+ t.integer "project_id", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "project_access", default: 0, null: false
+ t.integer "notification_level", default: 3, 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"
+ add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree
+ add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree
+ add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree
- create_table "web_hooks", :force => true do |t|
+ create_table "web_hooks", force: true do |t|
t.string "url"
t.integer "project_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "type", :default => "ProjectHook"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "type", default: "ProjectHook"
t.integer "service_id"
+ t.boolean "push_events", default: true, null: false
+ t.boolean "issues_events", default: false, null: false
+ t.boolean "merge_requests_events", default: false, null: false
+ t.boolean "tag_push_events", default: false
end
- add_index "web_hooks", ["project_id"], :name => "index_web_hooks_on_project_id"
+ add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
end
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000000..b73d7bb38e
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,24 @@
+**User documentation**
+
++ [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
++ [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
++ [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
++ [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
++ [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
++ [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
++ [Workflow](workflow/README.md) Learn how to use Git and GitLab together.
+
+**Administrator documentation**
+
++ [Install](install/README.md) Requirements, directory structures and manual installation.
++ [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
++ [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
++ [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
++ [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
++ [Update](update/README.md) Update guides to upgrade your installation.
+
+**Contributor documentation**
+
++ [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
++ [Legal](legal/README.md) Contributor license agreements.
++ [Release](release/README.md) How to make the monthly and security releases.
diff --git a/doc/api/README.md b/doc/api/README.md
index 517a9fae6f..4c40589fd4 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,5 +1,34 @@
# GitLab API
+## Resources
+
++ [Users](users.md)
++ [Session](session.md)
++ [Projects](projects.md)
++ [Project Snippets](project_snippets.md)
++ [Repositories](repositories.md)
++ [Repository Files](repository_files.md)
++ [Commits](commits.md)
++ [Branches](branches.md)
++ [Merge Requests](merge_requests.md)
++ [Issues](issues.md)
++ [Milestones](milestones.md)
++ [Notes](notes.md) (comments)
++ [Deploy Keys](deploy_keys.md)
++ [System Hooks](system_hooks.md)
++ [Groups](groups.md)
+
+## Clients
+
++ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
++ [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
++ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
++ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
++ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
++ [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
+
+## Introduction
+
All API requests require authentication. You need to pass a `private_token` parameter by url or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
@@ -103,6 +132,10 @@ When listing resources you can pass the following parameters:
+ `page` (default: `1`) - page number
+ `per_page` (default: `20`, max: `100`) - number of items to list per page
+[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response.
+These have `rel` prev/next/first/last and contain the relevant url.
+Please use these instead of generating your own urls.
+
## id vs iid
When you work with API you may notice two similar fields in api entites: id and iid.
@@ -117,28 +150,3 @@ Issue
So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`
But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
-
-
-
-## Contents
-
-+ [Users](users.md)
-+ [Session](session.md)
-+ [Projects](projects.md)
-+ [Project Snippets](project_snippets.md)
-+ [Repositories](repositories.md)
-+ [Merge Requests](merge_requests.md)
-+ [Issues](issues.md)
-+ [Milestones](milestones.md)
-+ [Notes](notes.md)
-+ [Deploy Keys](deploy_keys.md)
-+ [System Hooks](system_hooks.md)
-+ [Groups](groups.md)
-
-
-## Clients
-
-+ [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-+ [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-+ [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-+ [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
diff --git a/doc/api/branches.md b/doc/api/branches.md
new file mode 100644
index 0000000000..da9d4193f2
--- /dev/null
+++ b/doc/api/branches.md
@@ -0,0 +1,201 @@
+# Branches
+
+## List repository branches
+
+Get a list of repository branches from a project, sorted by name alphabetically.
+
+```
+GET /projects/:id/repository/branches
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
+
+```json
+[
+ {
+ "name": "master",
+ "commit": {
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "parents": [
+ {
+ "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ }
+ ],
+ "tree": "46e82de44b1061621357f24c05515327f2795a95",
+ "message": "add projects API",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "committer": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00"
+ },
+ "protected": true
+ }
+]
+```
+
+
+## Get single repository branch
+
+Get a single project repository branch.
+
+```
+GET /projects/:id/repository/branches/:branch
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `branch` (required) - The name of the branch
+
+```json
+{
+ "name": "master",
+ "commit": {
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "parents": [
+ {
+ "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ }
+ ],
+ "tree": "46e82de44b1061621357f24c05515327f2795a95",
+ "message": "add projects API",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "committer": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00"
+ },
+ "protected": true
+}
+```
+
+
+## Protect repository branch
+
+Protects a single project repository branch. This is an idempotent function, protecting an already
+protected repository branch still returns a `200 Ok` status code.
+
+```
+PUT /projects/:id/repository/branches/:branch/protect
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `branch` (required) - The name of the branch
+
+```json
+{
+ "name": "master",
+ "commit": {
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "parents": [
+ {
+ "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ }
+ ],
+ "tree": "46e82de44b1061621357f24c05515327f2795a95",
+ "message": "add projects API",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "committer": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00"
+ },
+ "protected": true
+}
+```
+
+
+## Unprotect repository branch
+
+Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
+unprotected repository branch still returns a `200 Ok` status code.
+
+```
+PUT /projects/:id/repository/branches/:branch/unprotect
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `branch` (required) - The name of the branch
+
+```json
+{
+ "name": "master",
+ "commit": {
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "parents": [
+ {
+ "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ }
+ ],
+ "tree": "46e82de44b1061621357f24c05515327f2795a95",
+ "message": "add projects API",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "committer": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ },
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00"
+ },
+ "protected": false
+}
+```
+
+## Create repository branch
+
+
+```
+POST /projects/:id/repository/branches
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `branch_name` (required) - The name of the branch
++ `ref` (required) - Create branch from commit sha or existing branch
+
+```json
+{
+ "name": "my-new-branch",
+ "commit": {
+ "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57",
+ "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module",
+ "parent_ids": [
+ "fff449e0bf453576f16c91d6544f00a2664009d8",
+ "f93a93626fec20fd659f4ed3ab2e64019b6169ae"
+ ],
+ "authored_date": "2014-02-20T19:54:55+02:00",
+ "author_name": "john smith",
+ "author_email": "john@example.com",
+ "committed_date": "2014-02-20T19:54:55+02:00",
+ "committer_name": "john smith",
+ "committer_email": "john@example.com"
+ },
+ "protected": false
+}
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
new file mode 100644
index 0000000000..241fe0e585
--- /dev/null
+++ b/doc/api/commits.md
@@ -0,0 +1,95 @@
+# Commits API
+
+## List repository commits
+
+Get a list of repository commits in a project.
+
+```
+GET /projects/:id/repository/commits
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "id": "ed899a2f4b50b4370feeea94676502b42383c746",
+ "short_id": "ed899a2f4b5",
+ "title": "Replace sanitize with escape once",
+ "author_name": "Dmitriy Zaporozhets",
+ "author_email": "dzaporozhets@sphereconsultinginc.com",
+ "created_at": "2012-09-20T11:50:22+03:00"
+ },
+ {
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00"
+ }
+]
+```
+
+## Get a single commit
+
+Get a specific commit identified by the commit hash or name of a branch or tag.
+
+```
+GET /projects/:id/repository/commits/:sha
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `sha` (required) - The commit hash or name of a repository branch or tag
+
+```json
+{
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00",
+ "committed_date": "2012-09-20T09:06:12+03:00",
+ "authored_date": "2012-09-20T09:06:12+03:00",
+ "parent_ids": [
+ "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
+ ]
+}
+```
+
+
+## Get the diff of a commit
+
+Get the diff of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/diff
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `sha` (required) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
+ "new_path": "doc/update/5.4-to-6.0.md",
+ "old_path": "doc/update/5.4-to-6.0.md",
+ "a_mode": null,
+ "b_mode": "100644",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false
+ }
+]
+```
+
+
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 50d2b82c36..d6c0e624df 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -16,19 +16,15 @@ Parameters:
[
{
"id": 1,
- "title" : "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "created_at":"2013-10-02T10:12:29Z"
+ "title": "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
- "title" : "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "created_at":"2013-10-02T11:12:29Z"
+ "title": "Another Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2013-10-02T11:12:29Z"
}
]
```
@@ -50,11 +46,9 @@ Parameters:
```json
{
"id": 1,
- "title" : "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
- "created_at":"2013-10-02T10:12:29Z"
+ "title": "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2013-10-02T10:12:29Z"
}
```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index ef37940faf..d18506f9ce 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -15,7 +15,7 @@ GET /issues
"project_id": 8,
"title": "4xx/5xx pages",
"description": "",
- "labels": [ ],
+ "labels": [],
"milestone": null,
"assignee": null,
"author": {
@@ -193,3 +193,7 @@ Parameters:
+ `id` (required) - The project ID
+ `issue_id` (required) - The ID of the issue
+
+## Comments on issues
+
+Comments are done via the notes resource.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2ddaea5a58..d68f34971f 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -1,45 +1,51 @@
## List merge requests
-Get all merge requests for this project. This function takes pagination parameters
-`page` and `per_page` to restrict the list of merge requests.
+Get all merge requests for this project.
+The `state` parameter can be used to get only merge requests with a
+given state (`opened`, `closed`, or `merged`) or all of them (`all`).
+The pagination parameters `page` and `per_page` can be used to restrict the
+list of merge requests.
```
GET /projects/:id/merge_requests
+GET /projects/:id/merge_requests?state=opened
+GET /projects/:id/merge_requests?state=all
```
Parameters:
+ `id` (required) - The ID of a project
++ `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed`
```json
[
- {
- "id":1,
- "iid":1,
- "target_branch":"master",
- "source_branch":"test1",
- "project_id":3,
- "title":"test1",
- "state":"opened",
- "upvotes":0,
- "downvotes":0,
- "author":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- },
- "assignee":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- }
+ {
+ "id": 1,
+ "iid": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "opened",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
}
+ }
]
```
@@ -59,31 +65,31 @@ Parameters:
```json
{
- "id":1,
- "iid":1,
- "target_branch":"master",
- "source_branch":"test1",
- "project_id":3,
- "title":"test1",
- "state":"merged",
- "upvotes":0,
- "downvotes":0,
- "author":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- },
- "assignee":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- }
+ "id": 1,
+ "iid": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "merged",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ }
}
```
@@ -99,37 +105,38 @@ POST /projects/:id/merge_requests
Parameters:
+ `id` (required) - The ID of a project
-+ `source_branch` (required) - The source branch
-+ `target_branch` (required) - The target branch
-+ `assignee_id` (optional) - Assignee user ID
-+ `title` (required) - Title of MR
++ `source_branch` (required) - The source branch
++ `target_branch` (required) - The target branch
++ `assignee_id` (optional) - Assignee user ID
++ `title` (required) - Title of MR
++ `target_project_id` (optional) - The target project (numeric id)
```json
{
- "id":1,
- "target_branch":"master",
- "source_branch":"test1",
- "project_id":3,
- "title":"test1",
- "state":"opened",
- "upvotes":0,
- "downvotes":0,
- "author":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- },
- "assignee":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- }
+ "id": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "opened",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ }
}
```
@@ -150,34 +157,82 @@ Parameters:
+ `target_branch` - The target branch
+ `assignee_id` - Assignee user ID
+ `title` - Title of MR
++ `state_event` - New state (close|reopen|merge)
```json
-
{
- "id":1,
- "target_branch":"master",
- "source_branch":"test1",
- "project_id":3,
- "title":"test1",
- "state":"opened",
- "upvotes":0,
- "downvotes":0,
- "author":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- },
- "assignee":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "state":"active",
- "created_at":"2012-04-29T08:46:00Z"
- }
+ "id": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "opened",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ }
+}
+```
+
+
+## Accept MR
+
+Merge changes submitted with MR usign this API.
+If merge success you get 200 OK.
+If it has some conflicts and can not be merged - you get 405 and error message 'Branch cannot be merged'
+If merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
+If you dont have permissions to accept this merge request - you get 401
+
+```
+PUT /projects/:id/merge_request/:merge_request_id/merge
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `merge_request_id` (required) - ID of MR
++ `merge_commit_message` (optional) - Custom merge commit message
+
+```json
+{
+ "id": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "merged",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ }
}
```
@@ -199,14 +254,59 @@ Parameters:
```json
{
- "author":{
- "id":1,
- "username": "admin",
- "email":"admin@local.host",
- "name":"Administrator",
- "blocked":false,
- "created_at":"2012-04-29T08:46:00Z"
- },
- "note":"text1"
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "note": "text1"
}
```
+
+
+## Get the comments on a MR
+
+Gets all the comments associated with a merge request.
+
+```
+GET /projects/:id/merge_request/:merge_request_id/comments
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `merge_request_id` (required) - ID of merge request
+
+```json
+[
+ {
+ "note": "this is the 1st comment on the 2merge merge request",
+ "author": {
+ "id": 11,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2014-03-06T08:17:35.000Z"
+ }
+ },
+ {
+ "note": "_Status changed to closed_",
+ "author": {
+ "id": 11,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2014-03-06T08:17:35.000Z"
+ }
+ }
+]
+```
+
+## Comments on issues
+
+Comments are done via the notes resource.
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index e899e28d21..2a2ef4b79b 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -9,15 +9,15 @@ GET /projects/:id/milestones
```json
[
{
- "id":12,
- "iid":3,
- "project_id":16,
- "title":"10.0",
- "description":"Version",
- "due_date":"2013-11-29",
- "state":"active",
- "updated_at":"2013-10-02T09:24:18Z",
- "created_at":"2013-10-02T09:24:18Z"
+ "id": 12,
+ "iid": 3,
+ "project_id": 16,
+ "title": "10.0",
+ "description": "Version",
+ "due_date": "2013-11-29",
+ "state": "active",
+ "updated_at": "2013-10-02T09:24:18Z",
+ "created_at": "2013-10-02T09:24:18Z"
}
]
```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 397aa87aad..e9ad6e00c7 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -1,3 +1,5 @@
+Notes can be wall notes or comments on snippets, issues or merge requests.
+
## Wall
### List project wall notes
@@ -17,7 +19,7 @@ Parameters:
{
"id": 522,
"body": "The solution is rather tricky",
- "attachment":null,
+ "attachment": null,
"author": {
"id": 1,
"username": "john_smith",
@@ -77,32 +79,32 @@ Parameters:
```json
[
{
- "id":302,
- "body":"_Status changed to closed_",
- "attachment":null,
- "author":{
- "id":1,
- "username":"pipin",
- "email":"admin@example.com",
- "name":"Pip",
- "state":"active",
- "created_at":"2013-09-30T13:46:01Z"
+ "id": 302,
+ "body": "_Status changed to closed_",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
},
- "created_at":"2013-10-02T09:22:45Z"
+ "created_at": "2013-10-02T09:22:45Z"
},
{
- "id":305,
- "body":"Text of the comment\r\n",
- "attachment":null,
- "author":{
- "id":1,
- "username":"pipin",
- "email":"admin@example.com",
- "name":"Pip",
- "state":"active",
- "created_at":"2013-09-30T13:46:01Z"
+ "id": 305,
+ "body": "Text of the comment\r\n",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
},
- "created_at":"2013-10-02T09:56:03Z"
+ "created_at": "2013-10-02T09:56:03Z"
}
]
```
@@ -169,20 +171,20 @@ Parameters:
```json
{
- "id":52,
- "title":"Snippet",
- "file_name":"snippet.rb",
- "author":{
- "id":1,
- "username":"pipin",
- "email":"admin@example.com",
- "name":"Pip",
- "state":"active",
- "created_at":"2013-09-30T13:46:01Z"
+ "id": 52,
+ "title": "Snippet",
+ "file_name": "snippet.rb",
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
},
- "expires_at":null,
- "updated_at":"2013-10-02T07:34:20Z",
- "created_at":"2013-10-02T07:34:20Z"
+ "expires_at": null,
+ "updated_at": "2013-10-02T07:34:20Z",
+ "created_at": "2013-10-02T07:34:20Z"
}
```
@@ -233,18 +235,18 @@ Parameters:
```json
{
- "id":301,
- "body":"Comment for MR",
- "attachment":null,
- "author":{
- "id":1,
- "username":"pipin",
- "email":"admin@example.com",
- "name":"Pip",
- "state":"active",
- "created_at":"2013-09-30T13:46:01Z"
+ "id": 301,
+ "body": "Comment for MR",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
},
- "created_at":"2013-10-02T08:57:14Z"
+ "created_at": "2013-10-02T08:57:14Z"
}
```
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index f7b7fc8fbb..e16e1e8459 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -57,7 +57,6 @@ Parameters:
+ `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
+ `code` (required) - The content of a snippet
@@ -75,7 +74,6 @@ Parameters:
+ `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
-+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (optional) - The content of a snippet
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 3ae9af59fc..ffaba0af7f 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -2,7 +2,7 @@
### List projects
-Get a list of projects owned by the authenticated user.
+Get a list of projects accessible by the authenticated user.
```
GET /projects
@@ -15,6 +15,7 @@ GET /projects
"description": null,
"default_branch": "master",
"public": false,
+ "visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
@@ -42,17 +43,19 @@ GET /projects
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
- }
+ },
+ "archived": false
},
{
"id": 6,
"description": null,
"default_branch": "master",
"public": false,
+ "visibility_level": 0,
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
"web_url": "http://example.com/brightbox/puppet",
- "owner": {
+ "owner": {
"id": 4,
"name": "Brightbox",
"created_at": "2013-09-30T13:46:02Z"
@@ -68,7 +71,7 @@ GET /projects
"snippets_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
- "namespace": {
+ "namespace": {
"created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 4,
@@ -76,12 +79,29 @@ GET /projects
"owner_id": 1,
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
- }
+ },
+ "archived": false
}
]
```
+#### List owned projects
+
+Get a list of projects owned by the authenticated user.
+
+```
+GET /projects/owned
+```
+
+#### List ALL projects
+
+Get a list of all GitLab projects (admin only).
+
+```
+GET /projects/all
+```
+
### Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user.
@@ -101,6 +121,7 @@ Parameters:
"description": null,
"default_branch": "master",
"public": false,
+ "visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
@@ -128,7 +149,18 @@ Parameters:
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
- }
+ },
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false
}
```
@@ -143,61 +175,66 @@ GET /projects/:id/events
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
```json
-
-[{
- "title": null,
- "project_id": 15,
- "action_name": "closed",
- "target_id": 830,
- "target_type": "Issue",
- "author_id": 1,
- "data": null,
- "target_title": "Public project search field"
-}, {
- "title": null,
- "project_id": 15,
- "action_name": "opened",
- "target_id": null,
- "target_type": null,
- "author_id": 1,
- "data": {
- "before": "50d4420237a9de7be1304607147aec22e4a14af7",
- "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "ref": "refs/heads/master",
- "user_id": 1,
- "user_name": "Dmitriy Zaporozhets",
- "repository": {
- "name": "gitlabhq",
- "url": "git@dev.gitlab.org:gitlab/gitlabhq.git",
- "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.",
- "homepage": "https://dev.gitlab.org/gitlab/gitlabhq"
- },
- "commits": [{
- "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "message": "Add simple search to projects in public area",
- "timestamp": "2013-05-13T18:18:08+00:00",
- "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
- "author": {
- "name": "Dmitriy Zaporozhets",
- "email": "dmitriy.zaporozhets@gmail.com"
- }
- }],
- "total_commits_count": 1
+[
+ {
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 830,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Public project search field"
},
- "target_title": null
-}, {
- "title": null,
- "project_id": 15,
- "action_name": "closed",
- "target_id": 840,
- "target_type": "Issue",
- "author_id": 1,
- "data": null,
- "target_title": "Finish & merge Code search PR"
-}]
+ {
+ "title": null,
+ "project_id": 15,
+ "action_name": "opened",
+ "target_id": null,
+ "target_type": null,
+ "author_id": 1,
+ "data": {
+ "before": "50d4420237a9de7be1304607147aec22e4a14af7",
+ "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "ref": "refs/heads/master",
+ "user_id": 1,
+ "user_name": "Dmitriy Zaporozhets",
+ "repository": {
+ "name": "gitlabhq",
+ "url": "git@dev.gitlab.org:gitlab/gitlabhq.git",
+ "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.",
+ "homepage": "https://dev.gitlab.org/gitlab/gitlabhq"
+ },
+ "commits": [
+ {
+ "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "message": "Add simple search to projects in public area",
+ "timestamp": "2013-05-13T18:18:08+00:00",
+ "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "email": "dmitriy.zaporozhets@gmail.com"
+ }
+ }
+ ],
+ "total_commits_count": 1
+ },
+ "target_title": null
+ },
+ {
+ "title": null,
+ "project_id": 15,
+ "action_name": "closed",
+ "target_id": 840,
+ "target_type": "Issue",
+ "author_id": 1,
+ "data": null,
+ "target_title": "Finish & merge Code search PR"
+ }
+]
```
@@ -212,14 +249,16 @@ POST /projects
Parameters:
+ `name` (required) - new project name
++ `namespace_id` (optional) - namespace for the new project (defaults to user)
+ `description` (optional) - short project description
-+ `default_branch` (optional) - 'master' by default
+ `issues_enabled` (optional)
+ `wall_enabled` (optional)
+ `merge_requests_enabled` (optional)
+ `wiki_enabled` (optional)
+ `snippets_enabled` (optional)
-+ `public` (optional)
++ `public` (optional) - if `true` same as setting visibility_level = 20
++ `visibility_level` (optional)
+* `import_url` (optional)
### Create project for user
@@ -241,7 +280,8 @@ Parameters:
+ `merge_requests_enabled` (optional)
+ `wiki_enabled` (optional)
+ `snippets_enabled` (optional)
-+ `public` (optional)
++ `public` (optional) - if `true` same as setting visibility_level = 20
++ `visibility_level` (optional)
## Remove project
@@ -269,7 +309,7 @@ GET /projects/:id/members
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `query` (optional) - Query string to search for members
@@ -283,7 +323,7 @@ GET /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `user_id` (required) - The ID of a user
```json
@@ -311,7 +351,7 @@ POST /projects/:id/members
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `user_id` (required) - The ID of a user to add
+ `access_level` (required) - Project access level
@@ -326,7 +366,7 @@ PUT /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `user_id` (required) - The ID of a team member
+ `access_level` (required) - Project access level
@@ -341,7 +381,7 @@ DELETE /projects/:id/members/:user_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `user_id` (required) - The ID of a team member
This method is idempotent and can be called multiple times with the same parameters.
@@ -362,7 +402,7 @@ GET /projects/:id/hooks
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
### Get project hook
@@ -375,13 +415,17 @@ GET /projects/:id/hooks/:hook_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `hook_id` (required) - The ID of a project hook
```json
{
"id": 1,
"url": "http://example.com/hook",
+ "project_id": 3,
+ "push_events": "true",
+ "issues_events": "true",
+ "merge_requests_events": "true",
"created_at": "2012-10-12T17:04:47Z"
}
```
@@ -397,8 +441,11 @@ POST /projects/:id/hooks
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `url` (required) - The hook URL
++ `push_events` - Trigger hook on push events
++ `issues_events` - Trigger hook on issues events
++ `merge_requests_events` - Trigger hook on merge_requests events
### Edit project hook
@@ -411,9 +458,12 @@ PUT /projects/:id/hooks/:hook_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `hook_id` (required) - The ID of a project hook
+ `url` (required) - The hook URL
++ `push_events` - Trigger hook on push events
++ `issues_events` - Trigger hook on issues events
++ `merge_requests_events` - Trigger hook on merge_requests events
### Delete project hook
@@ -427,7 +477,7 @@ DELETE /projects/:id/hooks/:hook_id
Parameters:
-+ `id` (required) - The ID or NAME of a project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `hook_id` (required) - The ID of hook to delete
Note the JSON response differs if the hook is available or not. If the project hook
@@ -446,39 +496,43 @@ GET /projects/:id/repository/branches
Parameters:
-+ `id` (required) - The ID of the project
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
```json
[
{
- "name":"async",
+ "name": "async",
"commit": {
- "id":"a2b702edecdf41f07b42653eb1abe30ce98b9fca",
- "parents": [{
- "id":"3f94fc7c85061973edc9906ae170cc269b07ca55"
- }],
+ "id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
+ "parents": [
+ {
+ "id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
+ }
+ ],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
- "message":"give caolan his credit where it's due (up top)",
+ "message": "give caolan credit where it's due (up top)",
"author": {
- "name":"Jeremy Ashkenas",
- "email":"jashkenas@example.com"
+ "name": "Jeremy Ashkenas",
+ "email": "jashkenas@example.com"
},
"committer": {
- "name":"Jeremy Ashkenas",
- "email":"jashkenas@example.com"
+ "name": "Jeremy Ashkenas",
+ "email": "jashkenas@example.com"
},
- "authored_date":"2010-12-08T21:28:50+00:00",
- "committed_date":"2010-12-08T21:28:50+00:00"
+ "authored_date": "2010-12-08T21:28:50+00:00",
+ "committed_date": "2010-12-08T21:28:50+00:00"
},
- "protected":false
+ "protected": false
},
{
"name": "gh-pages",
"commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc",
- "parents": [{
- "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
- }],
+ "parents": [
+ {
+ "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
+ }
+ ],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2",
"author": {
@@ -495,7 +549,6 @@ Parameters:
"protected": false
}
]
-
```
### List single branch
@@ -508,7 +561,7 @@ GET /projects/:id/repository/branches/:branch
Parameters:
-+ `id` (required) - The ID of the project.
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `branch` (required) - The name of the branch.
@@ -522,7 +575,7 @@ PUT /projects/:id/repository/branches/:branch/protect
Parameters:
-+ `id` (required) - The ID of the project.
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `branch` (required) - The name of the branch.
@@ -536,7 +589,7 @@ PUT /projects/:id/repository/branches/:branch/unprotect
Parameters:
-+ `id` (required) - The ID of the project.
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+ `branch` (required) - The name of the branch.
@@ -579,3 +632,29 @@ Parameters:
+ query (required) - A string contained in the project name
+ per_page (optional) - number of projects to return per page
+ page (optional) - the page to retrieve
+
+
+## Labels
+
+### List project labels
+
+Get a list of project labels.
+
+```
+GET /projects/:id/labels
+```
+
+Parameters:
+
++ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
+
+```json
+[
+ {
+ "name": "feature"
+ },
+ {
+ "name": "bug"
+ }
+]
+```
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 2769c22d6a..858fad0a0b 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -1,170 +1,3 @@
-## List repository branches
-
-Get a list of repository branches from a project, sorted by name alphabetically.
-
-```
-GET /projects/:id/repository/branches
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-
-```json
-[
- {
- "name": "master",
- "commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
- },
- "protected": true
- }
-]
-```
-
-
-## Get single repository branch
-
-Get a single project repository branch.
-
-```
-GET /projects/:id/repository/branches/:branch
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `branch` (required) - The name of the branch
-
-```json
-{
- "name": "master",
- "commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
- },
- "protected": true
-}
-```
-
-
-## Protect repository branch
-
-Protects a single project repository branch. This is an idempotent function, protecting an already
-protected repository branch still returns a `200 Ok` status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/protect
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `branch` (required) - The name of the branch
-
-```json
-{
- "name": "master",
- "commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
- },
- "protected": true
-}
-```
-
-
-## Unprotect repository branch
-
-Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
-unprotected repository branch still returns a `200 Ok` status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/unprotect
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `branch` (required) - The name of the branch
-
-```json
-{
- "name": "master",
- "commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
- },
- "protected": false
-}
-```
-
-
## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
@@ -183,9 +16,7 @@ Parameters:
"name": "v1.0.0",
"commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "parents": [
-
- ],
+ "parents": [],
"tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
"message": "Initial commit",
"author": {
@@ -204,94 +35,6 @@ Parameters:
]
```
-
-## List repository commits
-
-Get a list of repository commits in a project.
-
-```
-GET /projects/:id/repository/commits
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
-
-```json
-[
- {
- "id": "ed899a2f4b50b4370feeea94676502b42383c746",
- "short_id": "ed899a2f4b5",
- "title": "Replace sanitize with escape once",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dzaporozhets@sphereconsultinginc.com",
- "created_at": "2012-09-20T11:50:22+03:00"
- },
- {
- "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
- "short_id": "6104942438c",
- "title": "Sanitize for network graph",
- "author_name": "randx",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "created_at": "2012-09-20T09:06:12+03:00"
- }
-]
-```
-
-## Get a single commit
-
-Get a specific commit identified by the commit hash or name of a branch or tag.
-
-```
-GET /projects/:id/repository/commits/:sha
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `sha` (required) - The commit hash or name of a repository branch or tag
-
-```json
-{
- "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
- "short_id": "6104942438c",
- "title": "Sanitize for network graph",
- "author_name": "randx",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "created_at": "2012-09-20T09:06:12+03:00"
-}
-```
-
-
-## Get the diff of a commit
-
-Get the diff of a commit in a project.
-
-```
-GET /projects/:id/repository/commits/:sha/diff
-```
-
-Parameters:
-
-+ `id` (required) - The ID of a project
-+ `sha` (required) - The name of a repository branch or tag or if not given the default branch
-
-```json
-[
- {
- "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files",
- "new_path": "doc/update/5.4-to-6.0.md",
- "old_path": "doc/update/5.4-to-6.0.md",
- "a_mode": null,
- "b_mode": "100644",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false
- }
-]
-```
-
## List repository tree
Get a list of repository files and directories in a project.
@@ -307,45 +50,50 @@ Parameters:
+ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
```json
-
-[{
- "name": "assets",
- "type": "tree",
- "mode": "040000",
- "id": "6229c43a7e16fcc7e95f923f8ddadb8281d9c6c6"
-}, {
- "name": "contexts",
- "type": "tree",
- "mode": "040000",
- "id": "faf1cdf33feadc7973118ca42d35f1e62977e91f"
-}, {
- "name": "controllers",
- "type": "tree",
- "mode": "040000",
- "id": "95633e8d258bf3dfba3a5268fb8440d263218d74"
-}, {
- "name": "Rakefile",
- "type": "blob",
- "mode": "100644",
- "id": "35b2f05cbb4566b71b34554cf184a9d0bd9d46d6"
-}, {
- "name": "VERSION",
- "type": "blob",
- "mode": "100644",
- "id": "803e4a4f3727286c3093c63870c2b6524d30ec4f"
-}, {
- "name": "config.ru",
- "type": "blob",
- "mode": "100644",
- "id": "dfd2d862237323aa599be31b473d70a8a817943b"
-}]
-
+[
+ {
+ "name": "assets",
+ "type": "tree",
+ "mode": "040000",
+ "id": "6229c43a7e16fcc7e95f923f8ddadb8281d9c6c6"
+ },
+ {
+ "name": "contexts",
+ "type": "tree",
+ "mode": "040000",
+ "id": "faf1cdf33feadc7973118ca42d35f1e62977e91f"
+ },
+ {
+ "name": "controllers",
+ "type": "tree",
+ "mode": "040000",
+ "id": "95633e8d258bf3dfba3a5268fb8440d263218d74"
+ },
+ {
+ "name": "Rakefile",
+ "type": "blob",
+ "mode": "100644",
+ "id": "35b2f05cbb4566b71b34554cf184a9d0bd9d46d6"
+ },
+ {
+ "name": "VERSION",
+ "type": "blob",
+ "mode": "100644",
+ "id": "803e4a4f3727286c3093c63870c2b6524d30ec4f"
+ },
+ {
+ "name": "config.ru",
+ "type": "blob",
+ "mode": "100644",
+ "id": "dfd2d862237323aa599be31b473d70a8a817943b"
+ }
+]
```
-## Raw blob content
+## Raw file content
-Get the raw file contents for a file.
+Get the raw file contents for a file by commit sha and path.
```
GET /projects/:id/repository/blobs/:sha
@@ -358,6 +106,20 @@ Parameters:
+ `filepath` (required) - The path the file
+## Raw blob content
+
+Get the raw file contents for a blob by blob sha.
+
+```
+GET /projects/:id/repository/raw_blobs/:sha
+```
+
+Parameters:
+
++ `id` (required) - The ID of a project
++ `sha` (required) - The blob sha
+
+
## Get file archive
Get a an archive of the repository
@@ -368,4 +130,4 @@ GET /projects/:id/repository/archive
Parameters:
+ `id` (required) - The ID of a project
-+ `sha` (optional) - The commit sha to download defaults to the tip of the default branch
\ No newline at end of file
++ `sha` (optional) - The commit sha to download defaults to the tip of the default branch
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
new file mode 100644
index 0000000000..b215cc2500
--- /dev/null
+++ b/doc/api/repository_files.md
@@ -0,0 +1,102 @@
+# CRUD for repository files
+
+## Create, read, update and delete repository files using this API
+
+- - -
+
+## Get file from repository
+
+Allows you to receive information about file in repository like name, size, content.
+Note that file content is Base64 encoded.
+
+```
+GET /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "key.rb",
+ "file_path": "app/models/key.rb",
+ "size": 1476,
+ "encoding": "base64",
+ "content": "IyA9PSBTY2hlbWEgSW5mb3...",
+ "ref": "master",
+ "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
+ "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50"
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to new file. Ex. lib/class.rb
++ `ref` (required) - The name of branch, tag or commit
+
+## Create new file in repository
+
+```
+POST /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master"
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to new file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `encoding` (optional) - 'text' or 'base64'. Text is default.
++ `content` (required) - File content
++ `commit_message` (required) - Commit message
+
+## Update existing file in repository
+
+```
+PUT /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master"
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `encoding` (optional) - 'text' or 'base64'. Text is default.
++ `content` (required) - New file content
++ `commit_message` (required) - Commit message
+
+## Delete existing file in repository
+
+```
+DELETE /projects/:id/repository/files
+```
+
+Example response:
+
+```json
+{
+ "file_name": "app/project.rb",
+ "branch_name": "master"
+}
+```
+
+Parameters:
+
++ `file_path` (required) - Full path to file. Ex. lib/class.rb
++ `branch_name` (required) - The name of branch
++ `commit_message` (required) - Commit message
+
diff --git a/doc/api/session.md b/doc/api/session.md
index 162d4c8bf7..0be5af79da 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -26,11 +26,12 @@ __You can login with both GitLab and LDAP credentials now__
"skype": "",
"linkedin": "",
"twitter": "",
+ "website_url": "",
"dark_scheme": false,
"theme_id": 1,
"is_admin": false,
- "can_create_group" : true,
- "can_create_team" : true,
- "can_create_project" : true
+ "can_create_group": true,
+ "can_create_team": true,
+ "can_create_project": true
}
```
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 355ce31c12..d4c45ea9bb 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,70 +1,70 @@
-All methods require admin authorization.
-
-The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
-
-## List system hooks
-
-Get list of system hooks
-
-```
-GET /hooks
-```
-
-Parameters:
-
-+ **none**
-
-```json
-[
- {
- "id":3,
- "url":"http://example.com/hook",
- "created_at":"2013-10-02T10:15:31Z"
- }
-]
-```
-
-## Add new system hook hook
-
-```
-POST /hooks
-```
-
-Parameters:
-
-+ `url` (required) - The hook URL
-
-
-## Test system hook
-
-```
-GET /hooks/:id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of hook
-
-```json
-{
- "event_name":"project_create",
- "name":"Ruby",
- "path":"ruby",
- "project_id":1,
- "owner_name":"Someone",
- "owner_email":"example@gitlabhq.com"
-}
-```
-
-## Delete system hook
-
-Deletes a system hook. This is an idempotent API function and returns `200 Ok` even if the hook
-is not available. If the hook is deleted it is also returned as JSON.
-
-```
-DELETE /hooks/:id
-```
-
-Parameters:
-
-+ `id` (required) - The ID of hook
+All methods require admin authorization.
+
+The url endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
+
+## List system hooks
+
+Get list of system hooks
+
+```
+GET /hooks
+```
+
+Parameters:
+
++ **none**
+
+```json
+[
+ {
+ "id": 3,
+ "url": "http://example.com/hook",
+ "created_at": "2013-10-02T10:15:31Z"
+ }
+]
+```
+
+## Add new system hook hook
+
+```
+POST /hooks
+```
+
+Parameters:
+
++ `url` (required) - The hook URL
+
+
+## Test system hook
+
+```
+GET /hooks/:id
+```
+
+Parameters:
+
++ `id` (required) - The ID of hook
+
+```json
+{
+ "event_name": "project_create",
+ "name": "Ruby",
+ "path": "ruby",
+ "project_id": 1,
+ "owner_name": "Someone",
+ "owner_email": "example@gitlabhq.com"
+}
+```
+
+## Delete system hook
+
+Deletes a system hook. This is an idempotent API function and returns `200 Ok` even if the hook
+is not available. If the hook is deleted it is also returned as JSON.
+
+```
+DELETE /hooks/:id
+```
+
+Parameters:
+
++ `id` (required) - The ID of hook
diff --git a/doc/api/users.md b/doc/api/users.md
index 16479ea6e0..2b927c3077 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -20,6 +20,7 @@ GET /users
"skype": "",
"linkedin": "",
"twitter": "",
+ "website_url": "",
"extern_uid": "john.smith",
"provider": "provider_name",
"theme_id": 1,
@@ -38,6 +39,7 @@ GET /users
"skype": "",
"linkedin": "",
"twitter": "",
+ "website_url": "",
"extern_uid": "jack.smith",
"provider": "provider_name",
"theme_id": 1,
@@ -49,6 +51,10 @@ GET /users
]
```
+You can search for a users by email or username with:
+`/users?search=John`
+
+Also see `def search query` in `app/models/user.rb`.
## Single user
@@ -74,6 +80,7 @@ Parameters:
"skype": "",
"linkedin": "",
"twitter": "",
+ "website_url": "",
"extern_uid": "john.smith",
"provider": "provider_name",
"theme_id": 1,
@@ -102,6 +109,7 @@ Parameters:
+ `skype` (optional) - Skype ID
+ `linkedin` (optional) - Linkedin
+ `twitter` (optional) - Twitter account
++ `website_url` (optional) - Website url
+ `projects_limit` (optional) - Number of projects user can create
+ `extern_uid` (optional) - External UID
+ `provider` (optional) - External provider name
@@ -127,6 +135,7 @@ Parameters:
+ `skype` - Skype ID
+ `linkedin` - Linkedin
+ `twitter` - Twitter account
++ `website_url` - Website url
+ `projects_limit` - Limit projects each user can create
+ `extern_uid` - External UID
+ `provider` - External provider name
@@ -174,11 +183,12 @@ GET /user
"skype": "",
"linkedin": "",
"twitter": "",
+ "website_url": "",
"theme_id": 1,
"color_scheme_id": 2,
"is_admin": false,
- "can_create_group" : true,
- "can_create_project" : true
+ "can_create_group": true,
+ "can_create_project": true
}
```
@@ -195,17 +205,13 @@ GET /user/keys
[
{
"id": 1,
- "title" : "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "title": "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
},
{
"id": 3,
- "title" : "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "title": "Another Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
]
```
@@ -214,6 +220,18 @@ Parameters:
+ **none**
+## List SSH keys for user
+
+Get a list of a specified user's SSH keys. Available only for admin
+
+```
+GET /users/:uid/keys
+```
+
+Parameters:
+
++ `uid` (required) - id of specified user
+
## Single SSH key
@@ -230,10 +248,8 @@ Parameters:
```json
{
"id": 1,
- "title" : "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
- 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
- soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "title": "Public key",
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
```
@@ -282,3 +298,18 @@ Parameters:
+ `id` (required) - SSH key ID
+## Delete SSH key
+
+Deletes key owned by a specified user. Available only for admin.
+
+```
+DELETE /users/:uid/keys/:id
+```
+
+Parameters:
+
++ `uid` (required) - id of specified user
++ `id` (required) - SSH key ID
+
+Will return `200 Ok` on success, or `404 Not found` if either user or key cannot be found.
+
diff --git a/doc/development/README.md b/doc/development/README.md
new file mode 100644
index 0000000000..eb88b6c860
--- /dev/null
+++ b/doc/development/README.md
@@ -0,0 +1,5 @@
+## Development
+
++ [Architecture](architecture.md) of GitLab
++ [Shell commands](shell_commands.md) in the GitLab codebase
++ [Rake tasks](rake_tasks.md) for development
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
new file mode 100644
index 0000000000..f2f7a3d5f0
--- /dev/null
+++ b/doc/development/architecture.md
@@ -0,0 +1,183 @@
+# GitLab Architecture Overview
+---
+
+# Software delivery
+
+There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE).
+GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master).
+New versions of GitLab are released in stable branches and the master branch is for bleeding edge development.
+
+EE releases are available not long after CE releases.
+To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee).
+For more information about the release process see the section 'New versions and upgrading' in the readme.
+
+Both EE and CE require an add-on component called gitlab-shell.
+It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master).
+New versions are usually tags but staying on the master branch will give you the latest stable version.
+New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
+
+# System Layout
+
+When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
+
+GitLab is primarily installed within the `/home/git` user home directory as `git` user.
+Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
+The bare repositories are located in `/home/git/repositories`.
+GitLab is a ruby on rails application so the particulars of the inner workings can be learned by studying how a ruby on rails application works.
+To serve repositories over SSH there's an add-on application called gitlab-shell which is installed in `/home/git/gitlab-shell`.
+
+## Components
+
+
+
+A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS.
+It uses Nginx or Apache as a web front end to proxypass the Unicorn web server.
+By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported.
+The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets.
+GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server.
+It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incomming jobs.
+The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data).
+GitLab stores the bare git repositories it serves in `/home/git/repositories` by default.
+It also keeps default branch and hook information with the bare repository.
+`/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc.
+The satellite repository is used by the web interface for editing repositories and the wiki which is also a git repository.
+When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
+
+The add-on component gitlab-shell serves repositories over SSH.
+It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited.
+gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process.
+ gitlab-shell queries the GitLab API to determine authorization and access.
+
+## Installation Folder Summary
+
+To summarize here's the [directory structure of the `git` user home directory](../install/structure.md).
+
+
+## Processes
+
+ ps aux | grep '^git'
+
+GitLab has several components to operate.
+As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database.
+It also uses Apache httpd or nginx to proxypass Unicorn.
+As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default).
+Under the gitlab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
+
+## Repository access
+
+Repositories get accessed via HTTP or SSH.
+HTTP cloning/push/pull utilizes the GitLab API and SSH cloning is handled by gitlab-shell (previously explained).
+
+# Troubleshooting
+
+See the README for more information.
+
+## Init scripts of the services
+
+The GitLab init script starts and stops Unicorn and Sidekiq.
+
+```
+/etc/init.d/gitlab
+Usage: service gitlab {start|stop|restart|reload|status}
+```
+
+Redis (key-value store/non-persistent database)
+
+```
+/etc/init.d/redis
+Usage: /etc/init.d/redis {start|stop|status|restart|condrestart|try-restart}
+```
+
+SSH daemon
+
+```
+/etc/init.d/sshd
+Usage: /etc/init.d/sshd {start|stop|restart|reload|force-reload|condrestart|try-restart|status}
+```
+
+Web server (one of the following)
+
+```
+/etc/init.d/httpd
+Usage: httpd {start|stop|restart|condrestart|try-restart|force-reload|reload|status|fullstatus|graceful|help|configtest}
+
+$ /etc/init.d/nginx
+Usage: nginx {start|stop|restart|reload|force-reload|status|configtest}
+```
+
+Persistent database (one of the following)
+
+```
+/etc/init.d/mysqld
+Usage: /etc/init.d/mysqld {start|stop|status|restart|condrestart|try-restart|reload|force-reload}
+
+$ /etc/init.d/postgresql
+Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [version ..]
+```
+
+## Log locations of the services
+
+Note: `/home/git/` is shorthand for `/home/git`.
+
+gitlabhq (includes Unicorn and Sidekiq logs)
+
+* `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log`, `satellites.log`, and `unicorn.stderr.log` normally.
+
+gitlab-shell
+
+* `/home/git/gitlab-shell/gitlab-shell.log`
+
+ssh
+
+* `/var/log/auth.log` auth log (on Ubuntu).
+* `/var/log/secure` auth log (on RHEL).
+
+nginx
+
+* `/var/log/nginx/` contains error and access logs.
+
+Apache httpd
+
+* [Explanation of apache logs](http://httpd.apache.org/docs/2.2/logs.html).
+* `/var/log/apache2/` contains error and output logs (on Ubuntu).
+* `/var/log/httpd/` contains error and output logs (on RHEL).
+
+redis
+
+* `/var/log/redis/redis.log` there are also logrotated logs there.
+
+PostgreSQL
+
+* `/var/log/postgresql/*`
+
+MySQL
+
+* `/var/log/mysql/*`
+* `/var/log/mysql.*`
+
+## GitLab specific config files
+
+GitLab has configuration files located in `/home/git/gitlab/config/*`.
+Commonly referenced config files include:
+
+* `gitlab.yml` - GitLab configuration.
+* `unicorn.rb` - Unicorn web server settings.
+* `database.yml` - Database connection settings.
+
+gitlab-shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
+
+## Maintenance Tasks
+
+[GitLab](https://gitlab.com/gitlab-org/gitlab-ce/tree/master) provides rake tasks with which you see version information and run a quick check on your configuration to ensure it is configured properly within the application.
+See [maintenance rake tasks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/raketasks/maintenance.md).
+In a nutshell, do the following:
+
+```
+sudo -i -u git
+cd gitlab
+bundle exec rake gitlab:env:info RAILS_ENV=production
+bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`.
+While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL.
\ No newline at end of file
diff --git a/doc/development/gitlab_diagram_overview.odg b/doc/development/gitlab_diagram_overview.odg
new file mode 100644
index 0000000000..b7e02f8fa7
Binary files /dev/null and b/doc/development/gitlab_diagram_overview.odg differ
diff --git a/doc/development/gitlab_diagram_overview.png b/doc/development/gitlab_diagram_overview.png
new file mode 100644
index 0000000000..b5831cf0a4
Binary files /dev/null and b/doc/development/gitlab_diagram_overview.png differ
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
new file mode 100644
index 0000000000..9e75b3a627
--- /dev/null
+++ b/doc/development/rake_tasks.md
@@ -0,0 +1,25 @@
+# Rake tasks for developers
+
+## Setup db with developer seeds:
+
+Note that if your db user does not have advanced privilegies you must create db manually before run this command
+
+```
+bundle exec rake setup
+```
+
+## Run tests
+
+This runs all test suite present in GitLab
+
+```
+bundle exec rake test
+```
+
+## Generate searchable docs for source code
+
+You can find results under `doc/code` directory
+
+```
+bundle exec rake gitlab:generate_docs
+```
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
new file mode 100644
index 0000000000..af0d5ca442
--- /dev/null
+++ b/doc/development/shell_commands.md
@@ -0,0 +1,117 @@
+# Guidelines for shell commands in the GitLab codebase
+
+## References
+
+- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
+- [OWASP Command Injection](https://www.owasp.org/index.php/Command_Injection)
+- [Ruby on Rails Security Guide Command Line Injection](http://guides.rubyonrails.org/security.html#command-line-injection)
+
+## Use File and FileUtils instead of shell commands
+
+Sometimes we invoke basic Unix commands via the shell when there is also a Ruby API for doing it.
+Use the Ruby API if it exists.
+http://www.ruby-doc.org/stdlib-2.0.0/libdoc/fileutils/rdoc/FileUtils.html#module-FileUtils-label-Module+Functions
+
+```ruby
+# Wrong
+system "mkdir -p tmp/special/directory"
+# Better (separate tokens)
+system *%W(mkdir -p tmp/special/directory)
+# Best (do not use a shell command)
+FileUtils.mkdir_p "tmp/special/directory"
+
+# Wrong
+contents = `cat #{filename}`
+# Correct
+contents = File.read(filename)
+```
+
+This coding style could have prevented CVE-2013-4490.
+
+## Bypass the shell by splitting commands into separate tokens
+
+When we pass shell commands as a single string to Ruby, Ruby will let `/bin/sh` evaluate the entire string.
+Essentially, we are asking the shell to evaluate a one-line script.
+This creates a risk for shell injection attacks.
+It is better to split the shell command into tokens ourselves.
+Sometimes we use the scripting capabilities of the shell to change the working directory or set environment variables.
+All of this can also be achieved securely straight from Ruby
+
+```ruby
+# Wrong
+system "cd /home/git/gitlab && bundle exec rake db:#{something} RAILS_ENV=production"
+# Correct
+system({'RAILS_ENV' => 'production'}, *%W(bundle exec rake db:#{something}), chdir: '/home/git/gitlab')
+
+# Wrong
+system "touch #{myfile}"
+# Better
+system "touch", myfile
+# Best (do not run a shell command at all)
+FileUtils.touch myfile
+```
+
+This coding style could have prevented CVE-2013-4546.
+
+## Separate options from arguments with --
+
+Make the difference between options and arguments clear to the argument parsers of system commands with `--`.
+This is supported by many but not all Unix commands.
+
+To understand what `--` does, consider the problem below.
+
+```
+# Example
+$ echo hello > -l
+$ cat -l
+cat: illegal option -- l
+usage: cat [-benstuv] [file ...]
+```
+
+In the example above, the argument parser of `cat` assumes that `-l` is an option.
+The solution in the example above is to make it clear to `cat` that `-l` is really an argument, not an option.
+Many Unix command line tools follow the convention of separating options from arguments with `--`.
+
+```
+# Example (continued)
+$ cat -- -l
+hello
+```
+
+In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--`.
+
+```ruby
+# Wrong
+system(*%W(git branch -d #{branch_name}))
+# Correct
+system(*%W(git branch -d -- #{branch_name}))
+```
+
+This coding style could have prevented CVE-2013-4582.
+
+## Do not use the backticks
+
+Capturing the output of shell commands with backticks reads nicely, but you are forced to pass the command as one string to the shell.
+We explained above that this is unsafe.
+In the main GitLab codebase, the solution is to use `Gitlab::Popen.popen` instead.
+
+```ruby
+# Wrong
+logs = `cd #{repo_dir} && git log`
+# Correct
+logs, exit_status = Gitlab::Popen.popen(%W(git log), repo_dir)
+
+# Wrong
+user = `whoami`
+# Correct
+user, exit_status = Gitlab::Popen.popen(%W(whoami))
+```
+
+In other repositories, such as gitlab-shell you can also use `IO.popen`.
+
+```ruby
+# Safe IO.popen example
+logs = IO.popen(%W(git log), chdir: repo_dir).read
+```
+
+Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
diff --git a/doc/install/README.md b/doc/install/README.md
new file mode 100644
index 0000000000..ec80e3cd62
--- /dev/null
+++ b/doc/install/README.md
@@ -0,0 +1,4 @@
++ [Installation](installation.md)
++ [Requirements](requirements.md)
++ [Structure](structure.md)
++ [Database MySQL](database_mysql.md)
diff --git a/doc/install/databases.md b/doc/install/database_mysql.md
similarity index 57%
rename from doc/install/databases.md
rename to doc/install/database_mysql.md
index be7bc0aad2..bf8183729e 100644
--- a/doc/install/databases.md
+++ b/doc/install/database_mysql.md
@@ -1,22 +1,21 @@
-# Setup Database
-
-GitLab supports the following databases:
-
-* MySQL (preferred)
-* PostgreSQL
+## Note
+We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](http://bugs.mysql.com/bug.php?id=65830) that [suggested](http://bugs.mysql.com/bug.php?id=50909) [fixes](http://bugs.mysql.com/bug.php?id=65830) [have](http://bugs.mysql.com/bug.php?id=63164).
## MySQL
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
+
+ # Ensure you have MySQL version 5.5.14 or later
+ mysql --version
# Pick a database root password (can be anything), type it and press enter
# Retype the database root password and press enter
# Secure your installation.
sudo mysql_secure_installation
-
+
# Login to MySQL
mysql -u root -p
@@ -25,19 +24,23 @@ GitLab supports the following databases:
# Create a user for GitLab
# do not type the 'mysql>', this is part of the prompt
# change $password in the command below to a real password you pick
- mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
+ mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+ # Ensure you can use the InnoDB engine which is necessary to support long indexes.
+ # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
+ mysql> SET storage_engine=INNODB;
+
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
# Grant the GitLab user necessary permissions on the table.
- mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost';
+ mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
# Try connecting to the new database with the new user
- sudo -u git -H mysql -u gitlab -p -D gitlabhq_production
+ sudo -u git -H mysql -u git -p -D gitlabhq_production
# Type the password you replaced $password with earlier
@@ -47,25 +50,3 @@ GitLab supports the following databases:
mysql> \q
# You are done installing the database and can go back to the rest of the installation.
-
-
-## PostgreSQL
-
- # Install the database packages
- sudo apt-get install -y postgresql-9.1 libpq-dev
-
- # Login to PostgreSQL
- sudo -u postgres psql -d template1
-
- # Create a user for GitLab. (change $password to a real password)
- template1=# CREATE USER git;
-
- # Create the GitLab production database & grant all privileges on database
- template1=# CREATE DATABASE gitlabhq_production OWNER git;
-
- # Quit the database session
- template1=# \q
-
- # Try connecting to the new database with the new user
- sudo -u git -H psql -d gitlabhq_production
-
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 134ec3c1ec..eea5c763fc 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,20 +1,22 @@
# Select Version to Install
-Make sure you view this installation guide from the branch (version) of GitLab you would like to install. In most cases
-this should be the highest numbered stable branch (example shown below).
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases
+this should be the highest numbered stable branch (example shown below).
-
+
-If this is unclear check the [GitLab Blog](http://blog.gitlab.org/) for installation guide links by version.
+If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version.
# Important notes
-This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and operating system requirements.
+This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
-This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please consult [the installation section in the readme](https://github.com/gitlabhq/gitlabhq#installation).
+This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/).
+
+This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user.
-If you find a bug/error in this guide please **submit a pull request** following the [contributing guide](../../CONTRIBUTING.md).
+If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md).
- - -
@@ -25,10 +27,9 @@ The GitLab installation consists of setting up the following components:
1. Packages / Dependencies
2. Ruby
3. System Users
-4. GitLab shell
-5. Database
-6. GitLab
-7. Nginx
+4. Database
+5. GitLab
+6. Nginx
# 1. Packages / Dependencies
@@ -50,30 +51,10 @@ If you are not familiar with vim please skip this and keep using the default edi
sudo apt-get install -y vim
sudo update-alternatives --set editor /usr/bin/vim.basic
-Install the required packages:
+Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate
-Make sure you have the right version of Python installed.
-
- # Install Python
- sudo apt-get install -y python
-
- # 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 -y python2.7
-
- # Make sure you can access Python via python2
- python2 --version
-
- # If you get a "command not found" error create a link to the python binary
- sudo ln -s /usr/bin/python /usr/bin/python2
-
- # For reStructuredText markup language support install required package:
- sudo apt-get install -y python-docutils
-
Make sure you have the right version of Git installed
# Install Git
@@ -92,8 +73,8 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl --progress https://git-core.googlecode.com/files/git-1.8.4.1.tar.gz | tar xz
- cd git-1.8.4.1/
+ curl --progress https://git-core.googlecode.com/files/git-1.8.5.2.tar.gz | tar xz
+ cd git-1.8.5.2/
make prefix=/usr/local all
# Install into /usr/local/bin
@@ -105,12 +86,14 @@ Is the system packaged Git too old? Remove it and compile from source.
mail server. By default, Debian is shipped with exim4 whereas Ubuntu
does not ship with one. The recommended mail server is postfix and you can install it with:
- sudo apt-get install -y postfix
+ sudo apt-get install -y postfix
Then select 'Internet Site' and press enter to confirm the hostname.
# 2. Ruby
+The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby.
+
Remove the old Ruby 1.8 if present
sudo apt-get remove ruby1.8
@@ -118,8 +101,8 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p247.tar.gz | tar xz
- cd ruby-2.0.0-p247
+ curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz
+ cd ruby-2.0.0-p353
./configure --disable-install-rdoc
make
sudo make install
@@ -135,38 +118,31 @@ Create a `git` user for Gitlab:
sudo adduser --disabled-login --gecos 'GitLab' git
+# 4. Database
-# 4. GitLab shell
+We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md).
+NOTE: because we need to make use of extensions you need at least pgsql 9.1.
-GitLab Shell is an ssh access and repository management software developed specially for GitLab.
+ # Install the database packages
+ sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev
- # Go to home directory
- cd /home/git
+ # Login to PostgreSQL
+ sudo -u postgres psql -d template1
- # Clone gitlab shell
- sudo -u git -H git clone https://github.com/gitlabhq/gitlab-shell.git
+ # Create a user for GitLab.
+ template1=# CREATE USER git CREATEDB;
- cd gitlab-shell
+ # Create the GitLab production database & grant all privileges on database
+ template1=# CREATE DATABASE gitlabhq_production OWNER git;
- # switch to right version
- sudo -u git -H git checkout v1.7.4
+ # Quit the database session
+ template1=# \q
- sudo -u git -H cp config.yml.example config.yml
-
- # Edit config and replace gitlab_url
- # with something like 'http://domain.com/'
- sudo -u git -H editor config.yml
-
- # Do setup
- sudo -u git -H ./bin/install
+ # Try connecting to the new database with the new user
+ sudo -u git -H psql -d gitlabhq_production
-# 5. Database
-
-To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md).
-
-
-# 6. GitLab
+# 5. GitLab
# We'll install GitLab into home directory of the user "git"
cd /home/git
@@ -174,16 +150,13 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
## Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-8-stable gitlab
# Go to gitlab dir
cd /home/git/gitlab
- # Checkout to stable release
- sudo -u git -H git checkout 6-2-stable
-
**Note:**
-You can change `6-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+You can change `6-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
## Configure it
@@ -201,20 +174,18 @@ You can change `6-2-stable` to `master` if you want the *bleeding edge* version,
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
sudo chown -R git tmp/
- sudo chmod -R u+rwX log/
- sudo chmod -R u+rwX tmp/
+ sudo chmod -R u+rwX log/
+ sudo chmod -R u+rwX tmp/
# Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites
+ sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
- # Create directories for sockets/pids and make sure GitLab can write to them
- sudo -u git -H mkdir tmp/pids/
- sudo -u git -H mkdir tmp/sockets/
- sudo chmod -R u+rwX tmp/pids/
- sudo chmod -R u+rwX tmp/sockets/
+ # Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
+ sudo chmod -R u+rwX tmp/pids/
+ sudo chmod -R u+rwX tmp/sockets/
- # Create public/uploads directory otherwise backup will fail
- sudo -u git -H mkdir public/uploads
+ # Make sure GitLab can write to the public/uploads/ directory
sudo chmod -R u+rwX public/uploads
# Copy the example Unicorn config
@@ -227,10 +198,6 @@ You can change `6-2-stable` to `master` if you want the *bleeding edge* version,
# Copy the example Rack attack config
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
- # Enable rack attack middleware
- # Find and uncomment the line 'config.middleware.use Rack::Attack'
- sudo -u git -H editor config/application.rb
-
# Configure Git global settings for git user, useful when editing via web
# Edit user.email according to what is set in gitlab.yml
sudo -u git -H git config --global user.name "GitLab"
@@ -242,53 +209,74 @@ Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
## Configure GitLab DB settings
- # Mysql
+ # PostgreSQL only:
+ sudo -u git cp config/database.yml.postgresql config/database.yml
+
+ # MySQL only:
sudo -u git cp config/database.yml.mysql config/database.yml
- # Make sure to update username/password in config/database.yml.
+ # MySQL and remote PostgreSQL only:
+ # Update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
# If you followed the database guide then please do as follows:
# Change 'secure password' with the value you have given to $password
# You can keep the double quotes around the password
sudo -u git -H editor config/database.yml
- or
-
- # PostgreSQL
- sudo -u git cp config/database.yml.postgresql config/database.yml
-
-
+ # PostgreSQL and MySQL:
# Make config/database.yml readable to git only
sudo -u git -H chmod o-rwx config/database.yml
## Install Gems
+**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN`
+(where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable
+difference in completion time (~60% faster). Check the number of your cores with `nproc`.
+For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler).
+First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411)
+that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
+
cd /home/git/gitlab
- sudo gem install charlock_holmes --version '0.6.9.4'
-
- # For MySQL (note, the option says "without ... postgres")
- sudo -u git -H bundle install --deployment --without development test postgres aws
-
- # Or for PostgreSQL (note, the option says "without ... mysql")
+ # For PostgreSQL (note, the option says "without ... mysql")
sudo -u git -H bundle install --deployment --without development test mysql aws
+ # Or if you use MySQL (note, the option says "without ... postgres")
+ sudo -u git -H bundle install --deployment --without development test postgres aws
+
## Initialize Database and Activate Advanced Features
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
- # Type 'yes' to create the database.
+ # Type 'yes' to create the database tables.
# When done you see 'Administrator account created:'
+## Install GitLab shell
+
+GitLab Shell is an ssh access and repository management software developed specially for GitLab.
+
+ # Go to the Gitlab installation folder:
+ cd /home/git/gitlab
+
+ # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
+ sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.3] REDIS_URL=redis://localhost:6379 RAILS_ENV=production
+
+ # By default, the gitlab-shell config is generated from your main gitlab config. You can review (and modify) it as follows:
+ sudo -u git -H editor /home/git/gitlab-shell/config.yml
## Install Init Script
Download the init script (will be /etc/init.d/gitlab):
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
- sudo chmod +x /etc/init.d/gitlab
+
+And if you are installing with a non-default folder or user copy and edit the defaults file:
+
+ sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
+
+If you installed gitlab in another directory or as a user other than the default you should change these settings in /etc/default/gitlab. Do not edit /etc/init.d/gitlab as it will be changed on upgrade.
Make GitLab start on boot:
@@ -304,27 +292,22 @@ Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+## Compile assets
+
+ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+
## Start Your GitLab Instance
sudo service gitlab start
# or
sudo /etc/init.d/gitlab restart
-## Double-check Application Status
-To make sure you didn't miss anything run a more thorough check with:
-
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
-
-If all items are green, then congratulations on successfully installing GitLab!
-However there are still a few steps left.
-
-
-# 7. Nginx
+# 6. Nginx
**Note:**
Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the
-[GitLab recipes](https://github.com/gitlabhq/gitlab-recipes).
+[GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/).
## Installation
sudo apt-get install -y nginx
@@ -349,10 +332,20 @@ Make sure to edit the config file to match your setup:
# Done!
-Visit YOUR_SERVER for your first GitLab login.
+## Double-check Application Status
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations on successfully installing GitLab!
+
+## Initial Login
+
+Visit YOUR_SERVER in your web browser for your first GitLab login.
The setup has created an admin account for you. You can use it to log in:
- admin@local.host
+ root
5iveL!fe
**Important Note:**
@@ -367,6 +360,15 @@ nobody can access your GitLab by using this login information later on.
# Advanced Setup Tips
+## Additional markup styles
+
+Apart from the always supported markdown style there are other rich text files that GitLab can display.
+But you might have to install a dependency to do so.
+Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+For example, reStructuredText markup language support requires python-docutils:
+
+ sudo apt-get install -y python-docutils
+
## Custom Redis Connection
If you'd like Resque to connect to a Redis server on a non-standard port or on
@@ -376,7 +378,7 @@ a different host, you can configure its connection string via the
# example
production: redis://redis.example.tld:6379
-If you want to connect the Redis server via socket, then use the "unix:" URL scheme
+If you want to connect the Redis server via socket, then use the "unix:" URL scheme
and the path to the Redis socket file in the `config/resque.yml` file.
# example
@@ -409,10 +411,10 @@ These steps are fairly general and you will need to figure out the exact details
* Stop GitLab
`sudo service gitlab stop`
-* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example) as a reference)
+* Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) as a reference)
-* Add the gem to your [Gemfile](https://github.com/gitlabhq/gitlabhq/blob/master/Gemfile)
- `gem "omniauth-your-auth-provider"`
+* Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile)
+ `gem "omniauth-your-auth-provider"`
* If you're using MySQL, install the new Omniauth provider gem by running the following command:
`sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment`
@@ -428,5 +430,5 @@ These steps are fairly general and you will need to figure out the exact details
### Examples
If you have successfully set up a provider that is not shipped with GitLab itself, please let us know.
-You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Working-Custom-Omniauth-Provider-Configurations).
+You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations).
While we can't officially support every possible auth mechanism out there, we'd like to at least help those with special needs.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 30a9564674..fd2dd16cd8 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -1,58 +1,66 @@
# Operating Systems
-## Linux
+GitLab is developed for the Linux operating system. For the installations options and instructions please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
-GitLab is developed for the Linux operating system.
+## Supported Linux distributions
-GitLab officially supports (recent versions of) these Linux distributions:
-
-- Ubuntu Linux
-- Debian/GNU Linux
-
-It should also work on (though they are not officially supported):
-
-- Arch
+- Ubuntu
+- Debian
- CentOS
+- RedHat Enterprise Linux
+- Scientific Linux
+- Oracle Linux
+
+## Unsupported Linux distributions
+
+- Arch Linux
- Fedora
- Gentoo
-- RedHat
-## Other Unix Systems
+But on the above unsupported distributions is stll possible to install GitLab yourself with the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md).
-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!**
+## Unsupported Unix operating systems
-## Windows
+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 do this, please be aware it could be a lot of work.
+Please consider using a virtual machine to run GitLab.
-GitLab does **not** run on Windows and we have no plans of supporting it in the
-near future. Please consider using a virtual machine to run GitLab.
+## Other operating systems such as Windows
+
+GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
+Please consider using a virtual machine to run GitLab.
-# Rubies
+# Ruby versions
-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 requires Ruby (MRI) 1.9.3 or 2.0+.
+You will have to use the standard MRI implementation of Ruby.
+We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions.
# Hardware requirements
## CPU
-- 1 core works for under 100 users but the responsiveness might suffer
-- **2 cores** is the **recommended** number of cores and supports up to 100 users
-- 4 cores supports about 1,000 users
-- 8 cores supports up to 10,000 users
+- 1 core works supports up to 100 users but the application will not be responsive
+- **2 cores** is the **recommended** number of cores and supports up to 500 users
+- 4 cores supports up to 2,000 users
+- 8 cores supports up to 5,000 users
+- 16 cores supports up to 10,0000 users
+- 32 cores supports up to 20,0000 users
+- 64 cores supports up to 40,0000 users
## Memory
-- 512MB is too little memory, GitLab will be very slow and you will need 250MB of swap
-- 768MB is the minimal memory size but we advise against this
-- 1GB supports up to 100 users if you do not have individual repo's over 250MB
-- **2GB** is the **recommended** memory size and supports up to 1,000 users
-- 4GB supports up to 10,000 users
+- 512MB is the absolute minimum, you need 256MB of swap, you can configure only one slow unicorn worker, only ssh access will work, we do not recommend this
+- 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates using swap space)
+- **2GB** is the **recommended** memory size and supports up to 500 users
+- 4GB supports up to 2,000 users
+- 8GB supports up to 5,000 users
+- 16GB supports up to 10,000 users
+- 32GB supports up to 20,000 users
+- 64GB supports up to 40,000 users
## Storage
@@ -66,8 +74,14 @@ Apart from a local hard drive you can also mount a volume that supports the netw
If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
-# Installation troubles and reporting success or failure
+## Database
-If you have troubles installing GitLab following the [official installation guide](installation.md)
-or want to share your experience installing GitLab on a not officially supported
-platform, please follow the the [contribution guide](/CONTRIBUTING.md).
+If you want to run the database separately, the **recommended** database size is **1 MB per user**
+
+# Supported webbrowsers
+
+- Chrome (Latest stable version)
+- Firefox (Latest released version)
+- Safari 7+ (known problem: required fields in html5 do not work)
+- Opera (Latest released version)
+- IE 10+
diff --git a/doc/install/structure.md b/doc/install/structure.md
index f580ea159a..67ca189537 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -10,18 +10,12 @@ This is the directory structure you will end up with following the instructions
| |-- gitlab-shell
| |-- repositories
+* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
+* `/home/git/gitlab` - GitLab core software.
+* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory.
+* `/home/git/gitlab-shell` - Core add-on component of gitlab. Maintains SSH cloning and other functionality.
+* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
-**/home/git/.ssh**
+*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of gitlab and `config.yml` of gitlab-shell.*
-**/home/git/gitlab**
- This is where GitLab lives.
-
-**/home/git/gitlab-satellites**
- Contains a copy of all repositories with a working tree.
- It's used for merge requests, editing files, etc.
-
-**/home/git/repositories**
- Holds all your repositories in bare format.
- This is the place Git uses when you pull/push to your projects.
-
-You can change them in your `config/gitlab.yml` file.
+To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
diff --git a/doc/integration/README.md b/doc/integration/README.md
new file mode 100644
index 0000000000..8318113ce9
--- /dev/null
+++ b/doc/integration/README.md
@@ -0,0 +1,9 @@
+# GitLab Integration
+
+GitLab integrates with multiple third-party services to allow external issue trackers and external authentication.
+See the documentation below for details on how to configure these services.
+
++ [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
++ [LDAP](ldap.md) Set up sign in via LDAP
++ [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth.
++ [Slack](slack.md) Integrate with the Slack chat service
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
new file mode 100644
index 0000000000..e490b2f884
--- /dev/null
+++ b/doc/integration/external-issue-tracker.md
@@ -0,0 +1,9 @@
+GitLab has a great issue tracker but you can also use an external issue tracker such as JIRA or Redmine. This is something that you can turn on per GitLab project. If for example you configure JIRA it provides the following functionality:
+
+- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
+- clicking 'New issue' on the project dashboard creates a new JIRA issue;
+- To reference JIRA issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
+
+
+
+You can configure the integration in the gitlab.yml configuration file.
diff --git a/doc/integration/github.md b/doc/integration/github.md
new file mode 100644
index 0000000000..672536b4da
--- /dev/null
+++ b/doc/integration/github.md
@@ -0,0 +1,38 @@
+# GitHub OAuth2 OmniAuth Provider
+
+To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use.
+
+1. Sign in to GitHub.
+2. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
+3. Select "Applications" in the left menu.
+4. Select "Register new application".
+5. Provide the required details.
+ * Application name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
+ * Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
+ * Application description: Fill this in if you wish.
+ * Authorization callback URL: 'https://gitlab.company.com/users/auth/github/callback'
+6. Select "Register application".
+7. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. 
+8. On your GitLab server, open the configuration file.
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+9. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+10. Under `providers:` uncomment (or add) lines that look like the following:
+
+ ```
+ - { name: 'github', app_id: 'YOUR APP ID',
+ app_secret: 'YOUR APP SECRET',
+ args: { scope: 'user:email' } }
+ ```
+
+11. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+12. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+13. Save the configuration file.
+14. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/github_app.png b/doc/integration/github_app.png
new file mode 100644
index 0000000000..c0873b2e20
Binary files /dev/null and b/doc/integration/github_app.png differ
diff --git a/doc/integration/google.md b/doc/integration/google.md
new file mode 100644
index 0000000000..50f63502e7
--- /dev/null
+++ b/doc/integration/google.md
@@ -0,0 +1,49 @@
+# Google OAuth2 OmniAuth Provider
+
+To enable the Google OAuth2 OmniAuth provider you must register your application with Google. Google will generate a client ID and secret key for you to use.
+
+1. Sign in to the [Google Developers Console](https://console.developers.google.com/) with the Google account you want to use to register GitLab.
+2. Select "Create Project".
+3. Provide the project information
+ * Project name: 'GitLab' works just fine here.
+ * Project ID: Must be unique to all Google Developer registered applications. Google provides a randomly generated Project ID by default. You can use the randomly generated ID or choose a new one.
+4. Refresh the page. You should now see your new project in the list. Click on the project.
+5. Select "APIs & auth" in the left menu.
+6. Select "Credentials" in the submenu.
+7. Select "Create New Client ID".
+8. Fill in the required information
+ * Application type: "Web Application"
+ * Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
+ * Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
+9. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. 
+10. On your GitLab server, open the configuration file.
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+11. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+12. Under `providers:` uncomment (or add) lines that look like the following:
+
+ ```
+ - { name: 'google_oauth2', app_id: 'YOUR APP ID',
+ app_secret: 'YOUR APP SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ ```
+
+13. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+14. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+15. Save the configuration file.
+16. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a Google icon below the regular sign in form. Click the icon to begin the authentication process. Google will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+
+## Further Configuration
+
+This further configuration is not required for Google authentication to function but it is strongly recommended. Taking these steps will increase usability for users by providing a little more recognition and branding.
+
+At this point, when users first try to authenticate to your GitLab installation with Google they will see a generic application name on the prompt screen. The prompt informs the user that "Project Default Service Account" would like to access their account. "Project Default Service Account" isn't very recognizable and may confuse or cause users to be concerned. This is easily changeable.
+
+1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for instructions on how to get here if you closed your window).
+2. Scroll down until you find "Product Name". Change the product name to something more descriptive.
+3. Add any additional information as you wish - homepage, logo, privacy policy, etc. None of this is required, but it may help your users.
diff --git a/doc/integration/google_app.png b/doc/integration/google_app.png
new file mode 100644
index 0000000000..5a62ad3500
Binary files /dev/null and b/doc/integration/google_app.png differ
diff --git a/doc/integration/jira-integration-points.png b/doc/integration/jira-integration-points.png
new file mode 100644
index 0000000000..0692a7b458
Binary files /dev/null and b/doc/integration/jira-integration-points.png differ
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
new file mode 100644
index 0000000000..b52c4a4b5e
--- /dev/null
+++ b/doc/integration/ldap.md
@@ -0,0 +1,14 @@
+# GitLab LDAP integration
+
+GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
+The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user.
+GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+
+## Enabling LDAP sign-in for existing GitLab users
+
+When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
+If the LDAP email attribute is not found in GitLab's database, a new user is created.
+
+In other words, if an existing GitLab user wants to enable LDAP sign-in for themselves, they should check that their GitLab email address matches their LDAP email address, and then sign into GitLab via their LDAP credentials.
+GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
+If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
new file mode 100644
index 0000000000..84a5a8e8c2
--- /dev/null
+++ b/doc/integration/omniauth.md
@@ -0,0 +1,83 @@
+# OmniAuth
+
+GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services. Configuring
+OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can
+choose to sign in using any of the configured mechanisms.
+
++ [Initial OmniAuth Configuration](#initial-omniauth-configuration)
++ [Supported Providers](#supported-providers)
++ [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
+
+### Initial OmniAuth Configuration
+
+Before configuring individual OmniAuth providers there are a few global settings that need to be verified.
+
+1. Open the configuration file
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+2. Find the section dealing with OmniAuth. The section will look similar to the following.
+
+ ```
+ ## OmniAuth settings
+ omniauth:
+ # Allow login via Twitter, Google, etc. using OmniAuth providers
+ enabled: false
+
+ # CAUTION!
+ # This allows users to login without having a user account first (default: false).
+ # 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
+ # Uncomment the following lines and fill in the data of the auth provider you want to use
+ # If your favorite auth provider is not listed you can use others:
+ # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations
+ # The 'app_id' and 'app_secret' parameters are always passed as the first two
+ # arguments, followed by optional 'args' which can be either a hash or an array.
+ providers:
+ # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
+ # app_secret: 'YOUR APP SECRET',
+ # args: { access_type: 'offline', approval_prompt: '' } }
+ # - { name: 'twitter', app_id: 'YOUR APP ID',
+ # app_secret: 'YOUR APP SECRET'}
+ # - { name: 'github', app_id: 'YOUR APP ID',
+ # app_secret: 'YOUR APP SECRET',
+ # args: { scope: 'user:email' } }
+ ```
+
+3. Change `enabled` to `true`.
+4. Consider the next two configuration options: `allow_single_sign_on` and `block_auto_created_users`.
+ * `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
+ sign in via OmniAuth.
+ * `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
+ have to be unblocked by an administrator before they are able to sign in.
+ * **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
+ that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
+5. Choose one or more of the Supported Providers below to continue configuration.
+
+### Supported Providers
+
++ [GitHub](github.md)
++ [Google](google.md)
++ [Twitter](twitter.md)
+
+### Enable OmniAuth for an Existing User
+
+Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user
+originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an
+OmniAuth provider for an existing user.
+
+1. Sign in normally - whether standard sign in, LDAP, or another OmniAuth provider.
+2. Go to profile settings (the silhouette icon in the top right corner).
+3. Select the "Account" tab.
+4. Under "Social Accounts" select the desired OmniAuth provider, such as Twitter.
+5. The user will be redirected to the provider. Once the user authorized GitLab they will be redirected back to GitLab.
+
+The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
new file mode 100644
index 0000000000..057871a4b8
--- /dev/null
+++ b/doc/integration/slack.md
@@ -0,0 +1,34 @@
+# Slack integration
+
+### On Slack
+
+To enable Slack integration you must create an Incoming WebHooks integration on Slack;
+
+
+1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services)
+2. Click on the Integrations menu at the top of the page.
+3. Add a new Integration.
+4. Pick Incoming WebHooks
+5. Choose the channel name you want to send notifications to, in the Settings section
+6. Add Integrations.
+ * Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
+
+Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long.
+
+### On GitLab
+
+After Slack is ready we need to setup GitLab. Here are the steps to achieve this.
+
+
+1. Sign in to GitLab
+2. Pick the repository you want.
+3. Navigate to Settings -> Services -> Slack
+4. Fill in your Slack details
+ * Mark as active it
+ * Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain)
+ * Type in the token you got from Slack
+ * Type in the channel name you want to use (eg. #announcements)
+
+Have fun :)
+
+_P.S. You can set "branch,pushed,Compare changes" as highlight words on your Slack profile settings, so that you can be aware of new commits when somebody pushes them._
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
new file mode 100644
index 0000000000..0440e2f21f
--- /dev/null
+++ b/doc/integration/twitter.md
@@ -0,0 +1,47 @@
+# Twitter OAuth2 OmniAuth Provider
+
+To enable the Twitter OmniAuth provider you must register your application with Twitter. Twitter will generate a client
+ID and secret key for you to use.
+
+1. Sign in to [Twitter Developers](https://dev.twitter.com/) area.
+2. Hover over the avatar in the top right corner and select "My applications."
+3. Select "Create new app"
+4. Fill in the application details.
+ * Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or
+ something else descriptive.
+ * Description: Create a description.
+ * Website: The URL to your GitLab installation. 'https://gitlab.example.com'
+ * Callback URL: 'https://gitlab.example.com/users/auth/github/callback'
+ * Agree to the "Rules of the Road."
+ 
+6. Select "Create your Twitter application."
+7. Select the "Settings" tab.
+8. Underneath the Callback URL check the box next to "Allow this application to be used to Sign in the Twitter."
+9. Select "Update settings" at the bottom to save changes.
+10. Select the "API Keys" tab.
+11. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
+
+12. On your GitLab server, open the configuration file.
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+13. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration)
+for more details.
+14. Under `providers:` uncomment (or add) lines that look like the following:
+
+ ```
+ - { name: 'twitter', app_id: 'YOUR APP ID',
+ app_secret: 'YOUR APP SECRET' }
+ ```
+
+15. Change 'YOUR APP ID' to the API key from Twitter page in step 11.
+16. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11.
+17. Save the configuration file.
+18. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the
+authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/twitter_app_api_keys.png b/doc/integration/twitter_app_api_keys.png
new file mode 100644
index 0000000000..1076337172
Binary files /dev/null and b/doc/integration/twitter_app_api_keys.png differ
diff --git a/doc/integration/twitter_app_details.png b/doc/integration/twitter_app_details.png
new file mode 100644
index 0000000000..b95e8af8a7
Binary files /dev/null and b/doc/integration/twitter_app_details.png differ
diff --git a/doc/legal/README.md b/doc/legal/README.md
new file mode 100644
index 0000000000..ebfdad1354
--- /dev/null
+++ b/doc/legal/README.md
@@ -0,0 +1,2 @@
++ [Corporate contributor license agreement](corporate_contributor_license_agreement.md)
++ [Individual contributor license agreement](individual_contributor_license_agreement.md)
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
new file mode 100644
index 0000000000..bbc274f3b0
--- /dev/null
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -0,0 +1,25 @@
+You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions.
+
+1. Definitions.
+
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+
+2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+
+3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+
+4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation.
+
+5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
+
+6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+
+7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
+
+8. It is your responsibility to notify GitLab.com when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab.com.
+
+---------------------------------------
+
+This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md
new file mode 100644
index 0000000000..eaf5812ca4
--- /dev/null
+++ b/doc/legal/individual_contributor_license_agreement.md
@@ -0,0 +1,25 @@
+You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab.com. Except for the license granted herein to GitLab.com and recipients of software distributed by GitLab.com, You reserve all right, title, and interest in and to Your Contributions.
+
+1. Definitions.
+
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab.com. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab.com for inclusion in, or documentation of, any of the products owned or managed by GitLab.com (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab.com or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab.com for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+
+2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+
+3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab.com and to recipients of software distributed by GitLab.com a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+
+4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab.com, or that your employer has executed a separate Corporate CLA with GitLab.com.
+
+5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
+
+6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+
+7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab.com separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]".
+
+8. You agree to notify GitLab.com of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
+
+---------------------------------------
+
+This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/make_release.md b/doc/make_release.md
deleted file mode 100644
index 7d1115eca5..0000000000
--- a/doc/make_release.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Things to do when creating new release
-NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
-
-## Install guide up to date?
-
-* References correct GitLab branch `x-x-stable` and correct GitLab shell tag?
-
-## Make upgrade guide
-
-### From x.x to x.x
-
-#### 0. Any major changes? Database updates? Web server change? File structure changes?
-
-#### 1. Make backup
-
-#### 2. Stop server
-
-#### 3. Do users need to update dependencies like `git`?
-
-#### 4. Get latest code
-
-#### 5. Does GitLab shell need to be updated?
-
-#### 6. Install libs, migrations, etc.
-
-#### 7. Any config files updated since last release?
-
-Check if any of these changed since last release (~22nd of last month depending on when last release branch was created):
-
-* https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/nginx/gitlab
-* https://github.com/gitlabhq/gitlab-shell/commits/master/config.yml.example
-* https://github.com/gitlabhq/gitlabhq/commits/master/config/gitlab.yml.example
-* https://github.com/gitlabhq/gitlabhq/commits/master/config/unicorn.rb.example
-* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.mysql
-* https://github.com/gitlabhq/gitlabhq/commits/master/config/database.yml.postgresql
-
-#### 8. Need to update init script?
-
-Check if changed since last release (~22nd of last month depending on when last release branch was created): https://github.com/gitlabhq/gitlabhq/commits/master/lib/support/init.d/gitlab
-
-#### 9. Start application
-
-#### 10. Check application status
-
-## Make sure the code quality indicatiors are good
-
-* [](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
-
-* [](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
-
-* [](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-* [](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
-
-* [](https://coveralls.io/r/gitlabhq/gitlabhq)
-
-## Make a release branch
-
-After making the release branch new commits are cherry-picked from master. When the release gets closer we get more selective what is cherry-picked.
-
-* 5 days before release: feature freeze
-* 3 days before release: UI freeze
-* 1 day before release: code freeze
-
-# Write a blog post
-
-* Mention what GitLab is on the second line: GitLab is open source software to collaborate on code.
-* Select and thank the the Most Valuable Person (MVP) of this release.
-* Note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
-
-## Last actions
-
-1. Update VERSION and CHANGELOG
-1. Create a git tag vX.X.X
-1. Publish the blog post
-1. Tweet about the release
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index a84222f9fc..e7ebc61343 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -1,43 +1,55 @@
----------------------------------------------
-Table of Contents
+Table of Contents
=================
----------------------------------------------
-[GitLab Flavored Markdown](#toc_3)
--------------------------------
-[Newlines](#toc_4)
-[Multiple underscores in words](#toc_5)
-[URL autolinking](#toc_6)
-[Code and Syntax Highlighting](#toc_7)
-[Emoji](#toc_8)
-[Special GitLab references](#toc_9)
+**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
+
+[Newlines](#newlines)
+
+[Multiple underscores in words](#multiple-underscores-in-words)
+
+[URL autolinking](#url-autolinking)
+
+[Code and Syntax Highlighting](#code-and-syntax-highlighting)
+
+[Emoji](#emoji)
+
+[Special GitLab references](#special-gitlab-references)
+**[Standard Markdown](#standard-markdown)**
-[Standard Markdown](#toc_10)
-------------------------------
-[Headers](#toc_11)
-[Emphasis](#toc_20)
-[Lists](#toc_21)
-[Links](#toc_22)
-[Images](#toc_23)
-[Blockquotes](#toc_24)
-[Inline HTML](#toc_25)
-[Horizontal Rule](#toc_26)
-[Line Breaks](#toc_27)
-[Tables](#toc_28)
+[Headers](#headers)
-[References](#toc_29)
----------------------
+[Emphasis](#emphasis)
+
+[Lists](#lists)
+
+[Links](#links)
+
+[Images](#images)
+
+[Blockquotes](#blockquotes)
+
+[Inline HTML](#inline-html)
+
+[Horizontal Rule](#horizontal-rule)
+
+[Line Breaks](#line-breaks)
+
+[Tables](#tables)
+
+**[References](#references)**
----------------------------------------------
-
-GitLab Flavored Markdown (GFM)
+GitLab Flavored Markdown (GFM)
==============================
-For GitLab we developed something we call "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality.
+For GitLab we developed something we call "GitLab Flavored Markdown" (GFM).
+It extends the standard Markdown in a few significant ways to add some useful functionality.
You can use GFM in
@@ -49,20 +61,26 @@ You can use GFM in
* milestones
* wiki pages
-
+You can also use other rich text files in GitLab.
+You might have to install a depency to do so.
+Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+
Newlines
--------
-The biggest difference that GFM introduces is in the handling of linebreaks. With traditional Markdown you can hard wrap paragraphs of text and they will be combined into a single paragraph. We find this to be the cause of a huge number of unintentional formatting errors. GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended.
+GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p).
-The next paragraph contains two phrases separated by a single newline character:
+A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.:
Roses are red
Violets are blue
+ Sugar is sweet
+
Roses are red
Violets are blue
-
-
+
+Sugar is sweet
+
Multiple underscores in words
-----------------------------
It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words.
@@ -73,7 +91,6 @@ It is not reasonable to italicize just _part_ of a word, especially when you're
perform_complicated_task
do_this_and_do_that_and_another_thing
-
URL autolinking
---------------
GFM will autolink standard URLs you copy and paste into your text.
@@ -83,12 +100,10 @@ So if you want to link to a URL (instead of a textural link), you can simply put
http://www.google.com
-
## Code and Syntax Highlighting
Blocks of code are either fenced by lines with three back-ticks ```, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
-
```no-highlight
Inline `code` has `back-ticks around` it.
```
@@ -101,14 +116,14 @@ Example:
var s = "JavaScript syntax highlighting";
alert(s);
```
-
+
```python
def function():
#indenting works just fine in the fenced code block
s = "Python syntax highlighting"
print s
```
-
+
```ruby
require 'redcarpet'
markdown = Redcarpet.new("Hello World!")
@@ -116,7 +131,7 @@ Example:
```
```
- No language indicated, so no syntax highlighting.
+ No language indicated, so no syntax highlighting.
s = "There is no highlighting for this."
But let's throw in a tag.
```
@@ -147,7 +162,6 @@ s = "There is no highlighting for this."
But let's throw in a tag.
```
-
Emoji
-----
@@ -159,7 +173,7 @@ Emoji
If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
- Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
+ Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
Sometimes you want to be :cool: and add some :sparkles: to your :speech_balloon:. Well we have a :gift: for you:
@@ -169,9 +183,8 @@ You can use it to point out a :bug: or warn about :monkey:patches. And if someon
If you are :new: to this, don't be :fearful:. You can easily join the emoji :circus_tent:. All you need to do is to :book: up on the supported codes.
-Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
+Consult the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com/) for a list of all supported emoji codes. :thumbsup:
-
Special GitLab References
-----
@@ -179,7 +192,6 @@ GFM recognized special references.
You can easily reference e.g. a team member, an issue, or a commit within a project.
GFM will turn that reference into a link so you can navigate between them easily.
-
GFM will recognize the following:
* @foo : for team members
@@ -187,15 +199,12 @@ GFM will recognize the following:
* !123 : for merge requests
* $123 : for snippets
* 1234567 : for commits
-* [file](path/to/file) : for file references
-
-
+* \[file\](path/to/file) : for file references
----------------------------------
# Standard Markdown
----------------------------------
-
## Headers
```no-highlight
@@ -230,7 +239,54 @@ Alt-H1
Alt-H2
------
-
+### Header IDs and links
+
+All markdown rendered headers automatically get IDs, except for comments.
+
+On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
+
+The IDs are generated from the content of the header according to the following rules:
+
+1) remove the heading hashes `#` and process the rest of the line as it would be processed if it were not a header
+2) from the result, remove all HTML tags, but keep their inner content
+3) convert all characters to lowercase
+4) convert all characters except `[a-z0-9_-]` into hyphens `-`
+5) transform multiple adjacent hyphens into a single hyphen
+6) remove trailing and heading hyphens
+
+For example:
+
+```
+###### ..Ab_c-d. e [anchor](url) ..
+```
+
+which renders as:
+
+###### ..Ab_c-d. e [anchor](url) ..
+
+will first be converted by step 1) into a string like:
+
+```
+..Ab_c-d. e <a href="url">anchor</a> <img src="url" alt="alt text"/>..
+```
+
+After removing the tags in step 2) we get:
+
+```
+..Ab_c-d. e anchor ..
+```
+
+And applying all the other steps gives the id:
+
+```
+ab_c-d-e-anchor
+```
+
+Note in particular how:
+
+- for markdown anchors `[text](url)`, only the `text` is used
+- markdown images `` are completely ignored
+
## Emphasis
```no-highlight
@@ -251,18 +307,16 @@ Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~
-
-
## Lists
```no-highlight
1. First ordered list item
2. Another item
- * Unordered sub-list.
+ * Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
-4. And another item.
-
+4. And another item.
+
Some text that should be aligned with the above item.
* Unordered list can use asterisks
@@ -272,27 +326,26 @@ Strikethrough uses two tildes. ~~Scratch this.~~
1. First ordered list item
2. Another item
- * Unordered sub-list.
+ * Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
-4. And another item.
-
+4. And another item.
+
Some text that should be aligned with the above item.
* Unordered list can use asterisks
- Or minuses
+ Or pluses
-
## Links
-There are two ways to create links.
+There are two ways to create links, inline-style and reference-style.
[I'm an inline-style link](https://www.google.com)
[I'm a reference-style link][Arbitrary case-insensitive reference text]
- [I'm a relative reference to a repository file](../blob/master/LICENSE)
+ [I'm a relative reference to a repository file](LICENSE)
[You can use numbers for reference-style link definitions][1]
@@ -308,7 +361,7 @@ There are two ways to create links.
[I'm a reference-style link][Arbitrary case-insensitive reference text]
-[I'm a relative reference to a repository file](../blob/master/LICENSE)
+[I'm a relative reference to a repository file](LICENSE)
[You can use numbers for reference-style link definitions][1]
@@ -320,30 +373,37 @@ Some text to show that the reference links can follow later.
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com
-
+**Note**
+
+Relative links do not allow referencing project files in a wiki page or wiki page in a project file.
+The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
+
+`[I'm a reference-style link][style]`
+
+will point the link to `wikis/style` when the link is inside of a wiki markdown file.
+
## Images
Here's our logo (hover to see the title text):
- Inline-style:
- 
+ Inline-style:
+ 
- Reference-style:
- ![alt text][logo]
+ Reference-style:
+ ![alt text1][logo]
- [logo]: /assets/logo-white.png "Logo Title Text 2"
+ [logo]: assets/logo-white.png
-Here's our logo (hover to see the title text):
+Here's our logo:
-Inline-style:
-
+Inline-style:
+
-Reference-style:
+Reference-style:
![alt text][logo]
-[logo]: /assets/logo-white.png "Logo Title Text 2"
+[logo]: /assets/logo-white.png
-
## Blockquotes
```no-highlight
@@ -352,7 +412,7 @@ Reference-style:
Quote break.
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
```
> Blockquotes are very handy in email to emulate reply text.
@@ -360,12 +420,11 @@ Quote break.
Quote break.
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
-
## Inline HTML
-You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
```no-highlight
@@ -385,7 +444,6 @@ You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
- Does *not* work **very** well. Use HTML tags.
-
## Horizontal Rule
```
@@ -418,10 +476,9 @@ ___
Underscores
-
## Line Breaks
-My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
+My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
Here are some things to try out:
@@ -438,11 +495,9 @@ Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-This line is also begins a separate paragraph, but...
+This line is also begins a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-
## Tables
Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
@@ -461,10 +516,8 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
-
------------
-
## References
* This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
new file mode 100644
index 0000000000..9be6423f66
--- /dev/null
+++ b/doc/permissions/permissions.md
@@ -0,0 +1,45 @@
+Users have different abilities depending on the access level they have in a particular group or project.
+If a user is both in a project group and in the project itself, the highest permission level is used.
+If a user is a GitLab administrator they receive all permissions.
+
+---
+
+#### Project:
+
+
+| Action| Guest | Reporter | Developer | Master | Owner|
+|-------|-------|----------|-----------|--------|------|
+|Create new issue|✓|✓|✓|✓|✓|
+|Leave comments|✓|✓|✓|✓|✓|
+|Write on project wall|✓|✓|✓|✓|✓|
+|Pull project code| |✓|✓|✓|✓|
+|Download project| |✓|✓|✓|✓|
+|Create code snippets| |✓|✓|✓|✓|
+|Create new merge request| ||✓|✓|✓|
+|Create new branches| ||✓|✓|✓|
+|Push to non-protected branches| ||✓|✓|✓|
+|Remove non-protected branches| ||✓|✓|✓|
+|Add tags| ||✓|✓|✓|
+|Write a wiki| ||✓|✓|✓|
+|Manage issue tracker| ||✓|✓|✓|
+|Add new team members| |||✓|✓|
+|Push to protected branches| |||✓|✓|
+|Remove protected branches| |||✓|✓|
+|Edit project| |||✓|✓|
+|Add Deploy Keys to project| |||✓|✓|
+|Configure Project Hooks| |||✓|✓|
+|Switch visibility level| ||||✓|
+|Transfer project to another namespace| ||||✓|
+|Remove project| ||||✓|
+
+#### Group
+
+|Action|Guest|Reporter|Developer|Master|Owner|
+|------|-----|--------|---------|------|-----|
+|Browse group|✓|✓|✓|✓|✓|
+|Edit group|||||✓|
+|Create project in group|||||✓|
+|Manage group members|||||✓|
+|Remove group|||||✓|
+
+Any user can remove himself from a group, unless he is the last Owner of the group.
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
new file mode 100644
index 0000000000..76d83e6f3b
--- /dev/null
+++ b/doc/public_access/public_access.md
@@ -0,0 +1,28 @@
+Gitlab allows you to open selected projects to be accessed **publicly** or **internally**.
+Projects with either of these visibility levels will be listen in the [public access directory](/public).
+Internal projects will only be available to authenticated users.
+
+#### Public projects
+Public projects can be cloned **without any** authentication.
+It will also be listed on the [public access directory](/public).
+**Any logged in user** will have [Guest](/help/permissions) permissions on the repository.
+
+#### Internal projects
+Internal projects can be cloned by any logged in user.
+It will also be listed on the [public access directory](/public) for logged in users.
+Any logged in user will have [Guest](/help/permissions) permissions on the repository.
+
+#### How to change project visibility
+1. Go to your project dashboard
+2. Click on the "Edit" tab
+3. Change "Visibility Level"
+
+#### Visibility of users
+The public page of users, located at `/u/username` is visible if either:
+
+* You are logged in.
+* You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project.
+
+Otherwise, you will be redirected to the sign in page.
+
+When visiting the public page of an user, you will only see listed projects which you can view yourself.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
new file mode 100644
index 0000000000..6be24f0102
--- /dev/null
+++ b/doc/raketasks/README.md
@@ -0,0 +1,7 @@
++ [Backup restore](backup_restore.md)
++ [Cleanup](cleanup.md)
++ [Features](features.md)
++ [Maintenance](maintenance.md) and self-checks
++ [User management](user_management.md)
++ [Web hooks](web_hooks.md)
++ [Import](import.md) of git repositories in bulk
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index d2da64f3d3..bdff6ad5da 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -78,3 +78,18 @@ Restoring repositories:
- Restoring repository abcd... [DONE]
Deleting tmp directories...[DONE]
```
+
+### Configure cron to make daily backups
+
+```
+cd /home/git/gitlab
+sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
+sudo -u git crontab -e # Edit the crontab for the git user
+```
+
+Add the following lines at the bottom:
+
+```
+# Create a full backup of the GitLab repositories and SQL database every day at 2am
+0 2 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
new file mode 100644
index 0000000000..e11328dc5c
--- /dev/null
+++ b/doc/raketasks/import.md
@@ -0,0 +1,28 @@
+### Import bare repositories into GitLab project instance
+
+Notes:
+
+* project owner will be a first admin
+* groups will be created as needed
+* group owner will be the first admin
+* existing projects will be skipped
+
+How to use:
+
+1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
+2. run the command below
+
+```
+bundle exec rake gitlab:import:repos RAILS_ENV=production
+```
+
+Example output:
+
+```
+Processing abcd.git
+ * Created abcd (abcd.git)
+Processing group/xyz.git
+ * Created Group group (2)
+ * Created xyz (group/xyz.git)
+[...]
+```
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 3033d8c46b..2783c4153c 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -110,32 +110,3 @@ If necessary, remove the `tmp/repo_satellites` directory and rerun the command b
```
bundle exec rake gitlab:satellites:create RAILS_ENV=production
```
-
-### Import bare repositories into GitLab project instance
-
-Notes:
-
-* project owner will be a first admin
-* groups will be created as needed
-* group owner will be the first admin
-* existing projects will be skipped
-
-How to use:
-
-1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
-2. run the command below
-
-```
-bundle exec rake gitlab:import:repos RAILS_ENV=production
-```
-
-Example output:
-
-```
-Processing abcd.git
- * Created abcd (abcd.git)
-Processing group/xyz.git
- * Created Group group (2)
- * Created xyz (group/xyz.git)
-[...]
-```
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 8fa2ed1311..e823208291 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -1,6 +1,6 @@
### Add user as a developer to all projects
-```
+```bash
bundle exec rake gitlab:import:user_to_projects[username@domain.tld]
```
@@ -11,6 +11,22 @@ Notes:
* admin users are added as masters
-```
+```bash
bundle exec rake gitlab:import:all_users_to_all_projects
```
+
+### Add user as a developer to all groups
+
+```
+bundle exec rake gitlab:import:user_to_groups[username@domain.tld]
+```
+
+### Add all users to all groups
+
+Notes:
+
+* admin users are added as owners so they can add additional users to the group
+
+```
+bundle exec rake gitlab:import:all_users_to_all_groups
+```
diff --git a/doc/release/README.md b/doc/release/README.md
new file mode 100644
index 0000000000..22510be3f1
--- /dev/null
+++ b/doc/release/README.md
@@ -0,0 +1,2 @@
++ [Monthly](monthly.md)
++ [Security](security.md)
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
new file mode 100644
index 0000000000..514d73517b
--- /dev/null
+++ b/doc/release/monthly.md
@@ -0,0 +1,167 @@
+# Monthly Release
+NOTE: This is a guide for GitLab developers.
+
+# **15th - Code Freeze & Release Manager**
+
+### **1. Stop merging in code, except for important bugfixes**
+
+### **2. Release Manager**
+
+A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
+
+# **18th - Releasing RC1**
+
+The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
+
+### **1. Create an issue for RC1 release**
+
+### **2. Update the installation guide**
+
+1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
+2. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
+3. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
+4. There might be other changes. Ask around.
+
+### **3. Create an update guide**
+
+It's best to copy paste the previous guide and make changes where necessary. The typical steps are listed below with any points you should specifically look at.
+
+#### 0. Any major changes?
+List any major changes here, so the user is aware of them before starting to upgrade. For instance:
+- Database updates
+- Web server changes
+- File structure changes
+
+#### 1. Make backup
+
+#### 2. Stop server
+
+#### 3. Do users need to update dependencies like `git`?
+
+- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
+
+- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
+
+#### 4. Get latest code
+
+#### 5. Does GitLab shell need to be updated?
+
+#### 6. Install libs, migrations, etc.
+
+#### 7. Any config files updated since last release?
+
+Check if any of these changed since last release:
+
+* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab
+* https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example
+* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example
+* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example
+* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql
+* https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql
+
+#### 8. Need to update init script?
+
+Check if the init.d/gitlab script changed since last release: https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab
+
+#### 9. Start application
+
+#### 10. Check application status
+
+### **4. Code quality indicatiors**
+Make sure the code quality indicators are green / good.
+
+* [](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+
+* [](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch)
+
+* [](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+* [](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
+
+* [](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+### **5. Set VERSION**
+
+Set VERSION tot x.x.0.rc1
+
+
+### **6. Tag**
+
+Create an annotated tag that points to the version change commit.
+```
+git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
+```
+
+### **7. Tweet**
+
+Tweet about the RC release:
+
+> GitLab x.x.x.rc1 is out. This is a release candidate intended for testing only. Please let us know if you find regressions.
+
+### **8. Update Cloud**
+
+Merge the RC1 code into Cloud. Once the build is green, deploy in the morning.
+
+It is important to do this as soon as possible, so we can catch any errors before we release the full version.
+
+
+# **22nd - Release CE and EE**
+
+For GitLab EE, append -ee to the branches and tags.
+
+`x-x-stable-ee`
+
+`v.x.x.0-ee`
+
+### **1. Create x-x-stable branch and push to the repositories**
+
+```
+git checkout master
+git pull
+git checkout -b x-x-stable
+git push x-x-stable
+```
+
+### **2. Build the Omnibus packages**
+[Follow this guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
+
+### **3. QA**
+Use the omnibus packages to test using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md)
+
+
+### **4. Fix anything coming out of the QA**
+
+### **5. Set VERSION to x.x.0**
+
+### **6. Create annotated tag vx.x.0**
+```
+git tag -a vx.x.0 -m 'Version x.x.0'
+```
+
+### **7. Push VERSION + Tag to master, merge into x-x-stable**
+```
+git push origin master
+```
+
+Next, merge the VERSION into the x-x-stable branch.
+
+### **8. Push to remotes**
+
+For GitLab CE, push to dev, GitLab.com and GitHub.
+
+For GitLab EE, push to the subscribers repo.
+
+NOTE: You might not have the rights to push to master on dev. Ask Dmitriy.
+
+### **9. Publish blog for new release**
+* Mention what GitLab is on the second line: GitLab is open source software to collaborate on code.
+* Select and thank the the Most Valuable Person (MVP) of this release.
+* Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
+
+### **10. Tweet to blog**
+
+Send out a tweet to share the good news with the world. List the features in short and link to the blog post.
+
+# **23rd - Optional Patch Release**
+
+# **25th - Release GitLab CI**
diff --git a/doc/release/patch.md b/doc/release/patch.md
new file mode 100644
index 0000000000..30bb39b4e4
--- /dev/null
+++ b/doc/release/patch.md
@@ -0,0 +1,25 @@
+# Things to do when doing a patch release
+NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
+
+## When to do a patch release
+
+Do a patch release when there is a critical regression that needs to be adresses before the next monthly release.
+Otherwise include it in the monthly release and note there was a regression fix in the release announcement.
+
+## Release Procedure
+
+1. Verify that the issue can be repoduced
+1. Create an issue on private GitLab development server
+1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
+1. Fix the issue on a feature branch, do this on the private GitLab development server
+1. Consider creating and testing workarounds
+1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
+1. In a separate commit in the stable branch, update the VERSION and CHANGELOG
+1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
+1. Create an annotated tag vX.X.X for CE and another patch release for EE
+1. Make sure that the build has passed and no tests are failing
+1. Push the code and the tags to all the CE and EE repositories
+1. Apply the patch to GitLab Cloud and the private GitLab development server
+1. Send tweets about the release from @gitlabhq, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Build new packages with the latest version
+
diff --git a/doc/release/security.md b/doc/release/security.md
new file mode 100644
index 0000000000..2fe0a948ad
--- /dev/null
+++ b/doc/release/security.md
@@ -0,0 +1,70 @@
+# Things to do when doing an out-of-bound security release
+NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
+
+## When to do a security release
+
+Do a security release when there is a critical issue that needs to be adresses before the next monthly release. Otherwise include it in the monthly release and note there was a security fix in the release announcement.
+
+## Security vulnerability disclosure
+
+Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+
+## Release Procedure
+
+1. Verify that the issue can be repoduced
+1. Acknowledge the issue to the researcher that disclosed it
+1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
+1. Create feature branches for the blog post on GitLab.com and link them from the code branch
+1. Merge and publish the blog posts
+1. Send tweets about the release from @gitlabhq
+1. Send out an email to the subscribers mailing list on MailChimp
+1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
+1. Send out an email to [the GitLab newsletter list](http://gitlab.us5.list-manage.com/subscribe?u=498dccd07cf3e9482bee33ba4&id=98a9a4992c)
+1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
+1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/)
+1. Thank the security researcher in an email for their cooperation
+1. Update the blogpost and the CHANGELOG when we receive the CVE number
+
+The timing of the code merge into master should be coordinated in advance.
+After the merge we strive to publish the announcements within 60 minutes.
+
+## Blog post template
+
+XXX Security Advisory for GitLab
+
+A recently discovered critical vulnerability in GitLab allows [unauthenticated API access|remote code execution|unauthorized access to repositories|XXX|PICKSOMETHING]. All users should update GitLab and gitlab-shell immediately.
+We [have|haven't|XXX|PICKSOMETHING|] heard of this vulnerability being actively exploited.
+
+### Version affected
+
+GitLab Community Edition XXX and lower
+GitLab Enterprise Edition XXX and lower
+
+### Fixed versions
+
+GitLab Community Edition XXX and up
+GitLab Enterprise Edition XXX and up
+
+### Impact
+
+On GitLab installations which use MySQL as their database backend it is possible for an attacker to assume the identity of any existing GitLab user in certain API calls. This attack can be performed by [unauthenticated|authenticated|XXX|PICKSOMETHING] users.
+
+### Workarounds
+
+If you are unable to upgrade you should apply the following patch and restart GitLab.
+
+XXX
+
+### Credit
+
+We want to thank XXX of XXX for the reponsible disclosure of this vulnerability.
+
+## Email template
+
+We just announced a security advisory for GitLab at XXX
+
+Please contact us at support@gitlab.com if you have any questions.
+
+## Tweet template
+
+We just announced a security advisory for GitLab at XXX
diff --git a/doc/security/README.md b/doc/security/README.md
new file mode 100644
index 0000000000..f8dd1291b9
--- /dev/null
+++ b/doc/security/README.md
@@ -0,0 +1,2 @@
++ [Password length limits](password_length_limits.md)
++ [Rack attack](rack_attack.md)
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
new file mode 100644
index 0000000000..c8d66e9636
--- /dev/null
+++ b/doc/security/password_length_limits.md
@@ -0,0 +1,10 @@
+# Custom password length limits
+
+If you want to enforce longer user passwords you can create an extra Devise initializer with the steps below.
+If you do not use the `devise_password_length.rb` initializer the password length is set to a minimum of 8 characters in `config/initializers/devise.rb`.
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
+sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits
+```
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
new file mode 100644
index 0000000000..76d9e8bb07
--- /dev/null
+++ b/doc/ssh/README.md
@@ -0,0 +1,2 @@
++ [Deploy keys](deploy_keys.md)
++ [SSH](ssh.md)
diff --git a/doc/ssh/deploy_keys.md b/doc/ssh/deploy_keys.md
new file mode 100644
index 0000000000..c7125b7949
--- /dev/null
+++ b/doc/ssh/deploy_keys.md
@@ -0,0 +1,12 @@
+Deploy keys allow read-only access one or multiple projects with a single SSH key.
+
+This is really useful for cloning repositories to your Continuous Integration (CI) server.
+By using a deploy keys you don't have to setup a dummy user account.
+
+If you are a project master or owner you can add a deploy key in the project settings under the section Deploy Keys.
+Press the 'New Deploy Key' button and upload a public ssh key.
+After this the machine that uses the corresponding private key has read-only access to the project.
+
+You can't add the same deploy key twice with the 'New Deploy Key' option.
+If you want to add the same key to another project please enable it in the list that says 'Deploy keys from projects available to you'.
+All the deploy keys of all the projects you have access to are available. This project access can happen through being a direct member of the project or through a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more information.
diff --git a/doc/ssh/ssh.md b/doc/ssh/ssh.md
new file mode 100644
index 0000000000..0a38bc16b4
--- /dev/null
+++ b/doc/ssh/ssh.md
@@ -0,0 +1,24 @@
+SSH key allows you to establish a secure connection between your computer and GitLab
+
+
+Before generating an SSH key, check if your system already has one by running `cat ~/.ssh/id_rsa.pub`
+If your see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step.
+
+
+To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password.
+When prompted for the location and filename you can press enter to use the default.
+It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter.
+Note that the password you choose here can't be altered or retrieved.
+
+```bash
+ssh-keygen -t rsa -C "$your_email"
+```
+
+Use the code below to show your public key.
+
+```bash
+cat ~/.ssh/id_rsa.pub
+```
+
+Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile.
+Please copy the complete key starting with `ssh-` and ending with your username and host.
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
new file mode 100644
index 0000000000..5c8daf466a
--- /dev/null
+++ b/doc/system_hooks/system_hooks.md
@@ -0,0 +1,93 @@
+Your GitLab instance can perform HTTP POST requests on the following events: `create_project`, `delete_project`, `create_user`, `delete_user` and `change_team_member`.
+
+System hooks can be used, e.g. for logging or changing information in a LDAP server.
+
+#### Hooks request example:
+
+**Project created:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:54Z",
+ "event_name": "project_create",
+ "name": "StoreCloud",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "stormcloud",
+ "path_with_namespace": "jsmith/stormcloud",
+ "project_id": 74,
+ "project_visibility": "private",
+}
+```
+
+**Project destroyed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:58Z",
+ "event_name": "project_destroy",
+ "name": "Underscore",
+ "owner_email": "johnsmith@gmail.com",
+ "owner_name": "John Smith",
+ "path": "underscore",
+ "path_with_namespace": "jsmith/underscore",
+ "project_id": 73,
+ "project_visibility": "internal",
+}
+```
+
+**New Team Member:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_add_to_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "project_visibility": "private",
+}
+```
+
+**Team Member Removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_remove_from_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "project_visibility": "private",
+}
+```
+
+**User created:**
+
+```json
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_create",
+ "name": "John Smith",
+ "user_id": 41
+}
+```
+
+**User removed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:44:07Z",
+ "email": "js@gitlabhq.com",
+ "event_name": "user_destroy",
+ "name": "John Smith",
+ "user_id": 41
+}
+```
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index 90f59e1fd1..f8fb607e01 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -1,5 +1,8 @@
# From 4.2 to 5.0
+## Warning
+GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490.
+
## Important changes
* We don't use `gitlab` user any more. Everything will be moved to `git` user
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 45fc3436eb..ba56507dd8 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -1,5 +1,8 @@
# From 5.0 to 5.1
+## Warning
+GitLab 5.1 is affected by critical security vulnerability CVE-2013-4490.
+
## Release notes:
* `unicorn` replaced with `puma`
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index 27f992ecfe..466c815195 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -1,5 +1,8 @@
# From 5.1 to 5.2
+## Warning
+GitLab 5.2 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+
### 0. Backup
It's useful to make a backup just in case things go south:
@@ -48,8 +51,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
### 5. Update config files
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-2-stable/config/puma.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/puma.rb.example but with your settings.
### 6. Update Init script
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
new file mode 100644
index 0000000000..56f4854daf
--- /dev/null
+++ b/doc/update/5.1-to-5.4.md
@@ -0,0 +1,100 @@
+# From 5.1 to 5.4
+Also works starting from 5.2.
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch
+sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses CVE-2013-4489
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL
+sudo -u git -H bundle install --without development test postgres --deployment
+
+#PostgreSQL
+sudo -u git -H bundle install --without development test mysql --deployment
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+### 5. Update config files
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings.
+
+### 6. Update Init script
+
+```bash
+sudo rm /etc/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 7. Create uploads directory
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H mkdir public/uploads
+sudo chmod -R u+rwX public/uploads
+```
+
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade complete!
+
+## Things went south? Revert to previous version (5.3)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 5.2 to 5.3`](5.2-to-5.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index 6105d4b53b..5b74b1f893 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -1,5 +1,8 @@
# From 5.1 to 6.0
+## Warning
+GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+
### Deprecations
#### Global projects
@@ -44,7 +47,7 @@ sudo -u git -H git checkout 6-0-stable
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.7.0
+sudo -u git -H git checkout v1.7.9
```
### 4. Install additional packages
@@ -84,8 +87,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
Note: We switched from Puma in GitLab 5.x to unicorn in GitLab 6.0.
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/masterconfig/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/masterconfig/unicorn.rb.example but with your settings.
### 7. Update Init script
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index a8bb530902..e3c3016db6 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -1,5 +1,8 @@
# From 5.2 to 5.3
+## Warning
+GitLab 5.3 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+
### 0. Backup
It's useful to make a backup just in case things go south:
@@ -40,8 +43,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
### 4. Update config files
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-3-stable/config/puma.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/puma.rb.example but with your settings.
### 5. Update Init script
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 315bf03a6d..213ce77ec3 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -19,7 +19,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
sudo -u git -H git fetch
-sudo -u git -H git checkout 5-4-stable
+sudo -u git -H git checkout 5-4-stable # Latest version of 5-4-stable addresses CVE-2013-4489
```
### 3. Update gitlab-shell
@@ -27,7 +27,7 @@ sudo -u git -H git checkout 5-4-stable
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.5.0
+sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities
```
### 4. Install libs, migrations, etc.
@@ -48,8 +48,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
### 5. Update config files
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/puma.rb` same as https://github.com/gitlabhq/gitlabhq/blob/5-4-stable/config/puma.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings.
### 6. Update Init script
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index 3072a1da71..c289fcb57f 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -1,5 +1,8 @@
# From 5.4 to 6.0
+## Warning
+GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+
### Deprecations
#### Global projects
@@ -44,7 +47,7 @@ sudo -u git -H git checkout 6-0-stable
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.7.0
+sudo -u git -H git checkout v1.7.9
```
### 4. Install additional packages
@@ -84,14 +87,14 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
Note: We switched from Puma in GitLab 5.4 to unicorn in GitLab 6.0.
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://github.com/gitlabhq/gitlabhq/blob/master/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/unicorn.rb.example but with your settings.
### 7. Update Init script
```bash
sudo rm /etc/init.d/gitlab
-sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-0-stable/lib/support/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index 67770bd93d..b4cc920358 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -1,5 +1,8 @@
# From 6.0 to 6.1
+## Warning
+GitLab 6.1 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+
# In 6.1 we remove a lot of deprecated code.
# You should update to 6.0 before installing 6.1 so all the necessary conversions are run.
@@ -27,8 +30,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
-sudo -u git -H git fetch
+sudo -u git -H git fetch --all
sudo -u git -H git checkout 6-1-stable
+# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-1-stable-ee
```
### 3. Update gitlab-shell
@@ -36,7 +40,7 @@ sudo -u git -H git checkout 6-1-stable
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.7.4
+sudo -u git -H git checkout v1.7.9
```
### 4. Install libs, migrations, etc.
@@ -60,14 +64,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
### 5. Update config files
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-1-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/unicorn.rb.example but with your settings.
### 6. Update Init script
```bash
sudo rm /etc/init.d/gitlab
-sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-1-stable/lib/support/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/6.0-to-6.2.md b/doc/update/6.0-to-6.8.md
similarity index 63%
rename from doc/update/6.0-to-6.2.md
rename to doc/update/6.0-to-6.8.md
index 656bcaffdd..5c71e99aa5 100644
--- a/doc/update/6.0-to-6.2.md
+++ b/doc/update/6.0-to-6.8.md
@@ -1,4 +1,4 @@
-# From 6.0 to 6.2
+# From 6.0 to 6.8
# In 6.1 we remove a lot of deprecated code.
# You should update to 6.0 before installing 6.1 or higher so all the necessary conversions are run.
@@ -27,8 +27,21 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
-sudo -u git -H git fetch
-sudo -u git -H git checkout 6-2-stable
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-8-stable-ee
```
@@ -44,7 +57,7 @@ sudo apt-get install logrotate
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v1.7.4
+sudo -u git -H git checkout v1.9.3 # Addresses multiple critical security vulnerabilities
```
### 5. Install libs, migrations, etc.
@@ -52,18 +65,24 @@ sudo -u git -H git checkout v1.7.4
```bash
cd /home/git/gitlab
-# MySQL
+# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
+# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Enable internal issue IDs (introduced in GitLab 6.1)
sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
-sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Close access to gitlab-satellites for others
+sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
```
### 6. Update config files
@@ -71,17 +90,18 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
TIP: to see what changed in gitlab.yml.example in this release use next command:
```
-git diff 6-1-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 6-8-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-8-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-8-stable/config/unicorn.rb.example but with your settings.
+* Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-8-stable/lib/support/nginx/gitlab but with your settings.
* Copy rack attack middleware config
```bash
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
```
-* Uncomment `config.middleware.use Rack::Attack` in `/home/git/gitlab/config/application.rb`
+
* Set up logrotate
```bash
@@ -91,9 +111,7 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### 7. Update Init script
```bash
-sudo rm /etc/init.d/gitlab
-sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-2-stable/lib/support/init.d/gitlab
-sudo chmod +x /etc/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 8. Start application
@@ -126,3 +144,6 @@ Follow the [`upgrade guide from 5.4 to 6.0`](5.4-to-6.0.md), except for the data
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
+
+## Login issues after upgrade?
+If running in https mode, be sure to read [Can't Verify csrf token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page)
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index 6f0514a861..c618e599dc 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -20,18 +20,27 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```bash
cd /home/git/gitlab
-sudo -u git -H git fetch
-sudo -u git -H git checkout 6-2-stable
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout 6-2-stable # Latest version of 6-2-stable addresses CVE-2013-4489
+# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-2-stable-ee
```
-### 3. Install additional packages
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities
+```
+
+### 4. Install additional packages
```bash
# Add support for lograte for better log file handling
sudo apt-get install logrotate
```
-### 4. Install libs, migrations, etc.
+### 5. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -49,7 +58,7 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
-### 5. Update config files
+### 6. Update config files
TIP: to see what changed in gitlab.yml.example in this release use next command:
@@ -57,8 +66,8 @@ TIP: to see what changed in gitlab.yml.example in this release use next command:
git diff 6-1-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` same as https://github.com/gitlabhq/gitlabhq/blob/6-2-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/unicorn.rb.example but with your settings.
* Copy rack attack middleware config
```bash
@@ -71,20 +80,20 @@ sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
```
-### 6. Update Init script
+### 7. Update Init script
```bash
sudo rm /etc/init.d/gitlab
-sudo curl --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/6-2-stable/lib/support/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
-### 7. Start application
+### 8. Start application
sudo service gitlab start
sudo service nginx restart
-### 8. Check application status
+### 9. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md
new file mode 100644
index 0000000000..7f91604736
--- /dev/null
+++ b/doc/update/6.2-to-6.3.md
@@ -0,0 +1,109 @@
+# From 6.2 to 6.3
+
+## Requires version: 6.1 or 6.2
+
+### 0. Backup
+
+It's useful to make a backup just in case things go south:
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout 6-3-stable
+# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-3-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulnerabilities
+```
+
+The Gitlab-shell config changed recently, so check for config file changes and make `/home/git/gitlab-shell/config.yml` the same as https://github.com/gitlabhq/gitlab-shell/blob/master/config.yml.example
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+```
+
+### 5. Update config files
+
+TIP: to see what changed in gitlab.yml.example in this release use next command:
+
+```
+git diff 6-2-stable:config/gitlab.yml.example 6-3-stable:config/gitlab.yml.example
+```
+
+* Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/unicorn.rb.example but with your settings.
+
+```bash
+# Copy rack attack middleware config
+cd /home/git/gitlab
+sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
+```
+
+### 6. Update Init script
+
+```bash
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+```
+
+### 7. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 8. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade complete!
+
+## Things went south? Revert to previous version (6.2)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.1 to 6.2`](6.1-to-6.2.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md
new file mode 100644
index 0000000000..cfe75a6f14
--- /dev/null
+++ b/doc/update/6.3-to-6.4.md
@@ -0,0 +1,89 @@
+# From 6.3 to 6.4
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+````
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout 6-4-stable
+# For GitLab Enterprise Edition: sudo -u git -H git checkout 6-4-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.8.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check with:
+
+```bash
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations upgrade complete!
+
+## Things went south? Revert to previous version (6.3)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.2 to 6.3`](6.2-to-6.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md
new file mode 100644
index 0000000000..c88be3582c
--- /dev/null
+++ b/doc/update/6.4-to-6.5.md
@@ -0,0 +1,94 @@
+# From 6.4 to 6.5
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-5-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.8.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.4)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.3 to 6.4`](6.3-to-6.4.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md
new file mode 100644
index 0000000000..589c18c27c
--- /dev/null
+++ b/doc/update/6.5-to-6.6.md
@@ -0,0 +1,94 @@
+# From 6.5 to 6.6
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-6-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.8.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.5)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.4 to 6.5`](6.4-to-6.5.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md
new file mode 100644
index 0000000000..0f39c037c9
--- /dev/null
+++ b/doc/update/6.6-to-6.7.md
@@ -0,0 +1,101 @@
+# From 6.6 to 6.7
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-7-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.9.1
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+
+# Close access to gitlab-satellites for others
+sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
+```
+
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.6)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.5 to 6.6`](6.5-to-6.6.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/6.7-to-6.8.md b/doc/update/6.7-to-6.8.md
new file mode 100644
index 0000000000..457433c648
--- /dev/null
+++ b/doc/update/6.7-to-6.8.md
@@ -0,0 +1,125 @@
+# From 6.7 to 6.8
+
+### 0. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Get latest code
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H git fetch --all
+```
+
+For Gitlab Community Edition:
+
+```bash
+sudo -u git -H git checkout 6-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 6-8-stable-ee
+```
+
+### 3. Update gitlab-shell (and its config)
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v1.9.3
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab
+
+# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+
+# Close access to gitlab-satellites for others
+sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
+```
+
+### 5. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml if desired.
+
+```
+git diff 6-7-stable:config/gitlab.yml.example 6-8-stable:config/gitlab.yml.example
+```
+
+#### MySQL? Remove reaping frequency
+
+If you are using MySQL as a database, remove `reaping_frequency` from you database.yml to prevent crashes. [Relevant commit](https://gitlab.com/gitlab-org/gitlab-ce/commit/5163a8fcb9cfd63435560fda00173b76df2ccc93).
+
+#### HTTPS? Disable gzip
+
+If you are using HTTPS, disable gzip as in [this commit](https://gitlab.com/gitlab-org/gitlab-ce/commit/563fec734912d81cd7caea6fa8ec2b397fb72a9b) to prevent BREACH attacks.
+
+#### Turn on asset compression
+
+To improve performance, enable gzip asset compression as seen [in this commit](https://gitlab.com/gitlab-org/gitlab-ce/commit/8af94ed75505f0253823b9b2d44320fecea5b5fb).
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (6.7)
+
+### 1. Revert the code to the previous version
+Follow the [`upgrade guide from 6.6 to 6.7`](6.6-to-6.7.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
new file mode 100644
index 0000000000..9ce48a019e
--- /dev/null
+++ b/doc/update/README.md
@@ -0,0 +1,5 @@
++ [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
++ [Upgrader](upgrader.md)
++ [Ruby](ruby.md)
++ [Patch versions](patch_versions.md)
++ [MySQL to PostgreSQL](mysql_to_postgresql.md)
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
new file mode 100644
index 0000000000..5b9209d7df
--- /dev/null
+++ b/doc/update/mysql_to_postgresql.md
@@ -0,0 +1,68 @@
+# Migrating GitLab from MySQL to Postgres
+
+If you are replacing MySQL with Postgres while keeping GitLab on the same
+server all you need to do is to export from MySQL and import into Postgres as
+described below. If you are also moving GitLab to another server, or if you are
+switching to omnibus-gitlab, you may want to use a GitLab backup file. The
+second part of this documents explains the procedure to do this.
+
+## Export from MySQL and import into Postgres
+
+Use this if you are keeping GitLab on the same server.
+
+```
+sudo service gitlab stop
+
+# Update /home/git/gitlab/config/database.yml
+
+git clone https://github.com/lanyrd/mysql-postgresql-converter.git
+cd mysql-postgresql-converter
+mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
+python db_converter.py databasename.mysql databasename.psql
+psql -f databasename.psql -d gitlabhq_production
+
+sudo service gitlab start
+```
+
+## Converting a GitLab backup file from MySQL to Postgres
+
+GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using
+the lanyrd database converter we can replace a MySQL database dump inside the
+tar file with a Postgres database dump. This can be useful if you are moving to
+another server.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Create the backup
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+
+# Note the filename of the backup that was created. We will call it
+# TIMESTAMP_gitlab_backup.tar below.
+
+# Move the backup file we will convert to its own directory
+sudo -u git -H mkdir -p tmp/backups/postgresql
+sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql/
+
+# Create a separate database dump with PostgreSQL compatibility
+cd tmp/backups/postgresql
+sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production
+
+# Clone the database converter
+sudo -u git -H git clone https://github.com/lanyrd/mysql-postgresql-converter.git
+
+# Convert gitlabhq_production.mysql
+sudo -u git -H mkdir db
+sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql
+
+# Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
+
+# Warning: if you forget to replace TIMESTAMP below, tar will create a new file
+# 'TIMESTAMP_gitlab_backup.tar' without giving an error.
+
+sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql
+
+# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab installation.
+```
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 797fc81c92..2b947adaa1 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -1,4 +1,4 @@
-# Universal update guide for patch versions. Ex. from From 6.2.0 to 6.2.1
+# Universal update guide for patch versions. For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/).
### 0. Backup
@@ -14,36 +14,49 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
sudo service gitlab stop
-### 2. Get latest code for your current stable branch
+### 2. Get latest code for the stable branch
```bash
cd /home/git/gitlab
-sudo -u git -H git pull origin 6-2-stable
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout LATEST_TAG
```
-### 3. Install libs, migrations, etc.
+Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
+
+### 3. Update gitlab-shell if it is not the latest version
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout LATEST_TAG
+```
+
+Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
+
+### 4. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
-# MySQL
-sudo -u git -H bundle install --without development test postgres --deployment
-
#PostgreSQL
sudo -u git -H bundle install --without development test mysql --deployment
+# MySQL
+sudo -u git -H bundle install --without development test postgres --deployment
+
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
-### 4. Start application
+### 5. Start application
sudo service gitlab start
sudo service nginx restart
-### 5. Check application status
+### 6. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/ruby.md b/doc/update/ruby.md
new file mode 100644
index 0000000000..e98167f6b6
--- /dev/null
+++ b/doc/update/ruby.md
@@ -0,0 +1,54 @@
+# Updating Ruby from source
+
+This guide explains how to update Ruby in case you installed it from source according to the [instructions](../install/installation.md#2-ruby).
+
+### 1. Look for Ruby versions
+This guide will only update `/usr/local/bin/ruby`. You can see which Ruby binaries are installed on your system by running:
+
+```bash
+ls -l $(which -a ruby)
+```
+
+### 2. Stop GitLab
+
+```bash
+sudo service gitlab stop
+```
+
+### 3. Install or update dependencies
+Here we are assuming you are using Debian/Ubuntu.
+
+```bash
+sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl
+```
+
+### 4. Download, compile and install Ruby
+Find the latest stable version of Ruby 1.9 or 2.0 at https://www.ruby-lang.org/en/downloads/ . We recommend at least 2.0.0-p353, which is patched against [CVE-2013-4164](https://www.ruby-lang.org/en/news/2013/11/22/heap-overflow-in-floating-point-parsing-cve-2013-4164/).
+
+```bash
+cd /tmp
+curl --progress http://cache.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz
+cd ruby-2.0.0-p353
+./configure --disable-install-rdoc
+make
+sudo make install # overwrite the existing Ruby in /usr/local/bin
+sudo gem install bundler
+```
+
+### 5. Reinstall GitLab gem bundle
+Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle install` command [depends on your choice of database](../install/installation.md#install-gems).
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H rm -rf vendor/bundle # remove existing Gem bundle
+sudo -u git -H bundle install --deployment --without development test mysql aws # Assuming PostgreSQL
+```
+
+### 6. Start GitLab
+We are now ready to restart GitLab.
+
+```bash
+sudo service gitlab start
+```
+
+### Done
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
new file mode 100644
index 0000000000..72a94f67b3
--- /dev/null
+++ b/doc/update/upgrader.md
@@ -0,0 +1,53 @@
+# GitLab Upgrader
+
+GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
+For example it can update your application from 6.4 to latest GitLab 6 version (like 6.6.1).
+You still need to create a a backup and manually restart GitLab after runnning the script but all other operations are done by this upgrade script.
+If you have local changes to your GitLab repository the script will stash them and you need to use `git stash pop` after running the script.
+
+__GitLab Upgrader is available only for GitLab version 6.4.2 or higher__
+
+### 0. Backup
+
+ cd /home/git/gitlab
+ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Run gitlab upgrade tool
+
+ cd /home/git/gitlab
+ sudo -u git -H ruby script/upgrade.rb
+
+ # to perform a non-interactive install (no user input required) you can add -y
+ # sudo -u git -H ruby script/upgrade.rb -y
+
+### 3. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 4. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### One line upgrade command
+
+You've read through the entire guide, and probably did all the steps manually. Here is a one liner for convenience, the next time you upgrade:
+
+```bash
+cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
+ sudo service gitlab stop; sudo -u git -H ruby script/upgrade.rb -y; sudo service gitlab start; \
+ sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
new file mode 100644
index 0000000000..a223d83da3
--- /dev/null
+++ b/doc/web_hooks/web_hooks.md
@@ -0,0 +1,145 @@
+Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created.
+
+---
+
+You can configure web hooks to listen for specific events like pushes, issues or merge requests.
+GitLab will send a POST request with data to the web hook URL.
+Web hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
+If you send a web hook to an SSL endpoint [the certificate will not be verified](https://gitlab.com/gitlab-org/gitlab-ce/blob/ccd617e58ea71c42b6b073e692447d0fe3c00be6/app/models/web_hook.rb#L35) since many people use self-signed certificates.
+
+---
+
+#### Push events
+
+Triggered when you push to the repository except when pushing tags.
+
+**Request body:**
+
+```json
+{
+ "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+ "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "ref": "refs/heads/master",
+ "user_id": 4,
+ "user_name": "John Smith",
+ "project_id": 15,
+ "repository": {
+ "name": "Diaspora",
+ "url": "git@localhost:diaspora.git",
+ "description": "",
+ "homepage": "http://localhost/diaspora"
+ },
+ "commits": [
+ {
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "message": "Update Catalan translation to e38cb41.",
+ "timestamp": "2011-12-12T14:27:31+02:00",
+ "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "author": {
+ "name": "Jordi Mallach",
+ "email": "jordi@softcatala.org"
+ }
+ },
+ {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ }
+ ],
+ "total_commits_count": 4
+}
+```
+
+#### Issues events
+
+Triggered when a new issue is created or an existing issue was updated/closed/reopened.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "issue",
+ "object_attributes": {
+ "id": 301,
+ "title": "New API: create/update/delete file",
+ "assignee_id": 51,
+ "author_id": 51,
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "position": 0,
+ "branch_name": null,
+ "description": "Create new API for manipulations with repository",
+ "milestone_id": null,
+ "state": "opened",
+ "iid": 23
+ }
+}
+```
+
+#### Merge request events
+
+Triggered when a new merge request is created or an existing merge request was updated/merged/closed.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "merge_request",
+ "object_attributes": {
+ "id": 99,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2013-12-03T17:23:34Z",
+ "updated_at": "2013-12-03T17:23:34Z",
+ "st_commits": null,
+ "st_diffs": null,
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": ""
+ }
+}
+```
+
+#### Example webhook receiver
+
+If you want to see GitLab's webhooks in action for testing purposes you can use
+a simple echo script running in a console session.
+
+Save the following file as `print_http_body.rb`.
+
+```ruby
+require 'webrick'
+
+server = WEBrick::HTTPServer.new(Port: ARGV.first)
+server.mount_proc '/' do |req, res|
+ puts req.body
+end
+
+trap 'INT' do server.shutdown end
+server.start
+```
+
+Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
+8000`. Then add your server as a webhook receiver in GitLab as
+`http://my.host:8000/`.
+
+When you press 'Test Hook' in GitLab, you should see something like this in the console.
+
+```
+{"before":"077a85dd266e6f3573ef7e9ef8ce3343ad659c4e","after":"95cd4a99e93bc4bbabacfa2cd10e6725b1403c60",}
+localhost - - [14/May/2014:07:45:26 EDT] "POST / HTTP/1.1" 200 0
+- -> /
+```
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
new file mode 100644
index 0000000000..efc28d06e7
--- /dev/null
+++ b/doc/workflow/README.md
@@ -0,0 +1,2 @@
++ [Workflow](workflow/workflow.md)
++ [Project Features](workflow/project_features.md)
diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md
new file mode 100644
index 0000000000..4e07d7c04c
--- /dev/null
+++ b/doc/workflow/authorization_for_merge_requests.md
@@ -0,0 +1,32 @@
+There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project.
+
+## Protected branch flow
+
+With the protected branch flow everybody works within the same GitLab project.
+The project maintainers get Master access and the regular developers get Developer access.
+The maintainers mark the authoritative branches as 'Protected'.
+The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches.
+Only users with Master access can merge changes into a protected branch.
+
+### Advantages
+
+- fewer projects means less clutter
+- developers need to consider only one remote repository
+
+### Disadvantages
+
+- manual setup of protected branch required for each new project
+
+## Forking workflow
+
+With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it.
+Developers create forks of the authoritative project and push their feature branches to their own forks.
+To get their changes into master they need to create a merge request across forks.
+
+### Advantages
+
+- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects
+
+### Disadvantages
+
+- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes)
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
new file mode 100644
index 0000000000..25fe403257
--- /dev/null
+++ b/doc/workflow/project_features.md
@@ -0,0 +1,35 @@
+When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
+Below you will find a more elaborate explanation of each of these.
+
+
+## Issues
+
+Issues is a really powerful, but lightweight issue tracking system.
+You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
+They integrate deeply into GitLab and are easily referenced from anywhere by using # and the issuenumber.
+At GitLab.com, we use this for all our project management needs.
+
+## Merge Requests
+
+Using a merge request, you can review and discuss code before it is merged in the branch of your code.
+As with issues, it can be assigned; people, issues, etc. can be refereced; milestones attached.
+We see it as an integral part of working together on code and couldn't work without it.
+
+
+## Wiki
+
+This is a separate system for documentation, built right into GitLab.
+It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
+
+
+## Wall
+
+For simple, project specific conversations, the wall can be used.
+It's very lightweight and simple and works well if you're not interested in using issues, but still want to occasionally communicate within a project.
+
+
+## Snippets
+
+Snippets are little bits of code or text.
+This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
+For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md
new file mode 100644
index 0000000000..bb232e9d5c
--- /dev/null
+++ b/doc/workflow/workflow.md
@@ -0,0 +1,27 @@
+1. Clone project
+
+ ```bash
+ git clone git@example.com:project-name.git
+ ```
+
+2. Create branch with your feature
+
+ ```bash
+ git checkout -b $feature_name
+ ```
+
+3. Write code. Commit changes
+
+ ```bash
+ git commit -am "My feature is ready"
+ ```
+
+4. Push your branch to GitLab
+
+ ```bash
+ git push origin $feature_name
+ ```
+
+5. Review your code on commits page
+6. Create a merge request
+7. Your team lead will review the code & merge it to the main branch
diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature
index 226d3d5d5b..15fcda45e4 100644
--- a/features/admin/active_tab.feature
+++ b/features/admin/active_tab.feature
@@ -27,6 +27,11 @@ Feature: Admin active tab
Then the active main tab should be Logs
And no other main tabs should be active
+ Scenario: On Admin Messages
+ Given I visit admin messages page
+ Then the active main tab should be Messages
+ And no other main tabs should be active
+
Scenario: On Admin Hooks
Given I visit admin hooks page
Then the active main tab should be Hooks
diff --git a/features/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature
new file mode 100644
index 0000000000..5f16120b7c
--- /dev/null
+++ b/features/admin/broadcast_messages.feature
@@ -0,0 +1,20 @@
+Feature: Admin Broadcast Messages
+ Background:
+ Given I sign in as an admin
+ And application already has admin messages
+ And I visit admin messages page
+
+ Scenario: See broadcast messages list
+ Then I should be all broadcast messages
+
+ Scenario: Create a broadcast message
+ When submit form with new broadcast message
+ Then I should be redirected to admin messages page
+ And I should see newly created broadcast message
+
+ Scenario: Create a customized broadcast message
+ When submit form with new customized broadcast message
+ Then I should be redirected to admin messages page
+ And I should see newly created broadcast message
+ Then I visit dashboard page
+ And I should see a customized broadcast message
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 6fed9a3486..352c1b3803 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -2,7 +2,7 @@ Feature: Admin Groups
Background:
Given I sign in as an admin
And I have group with projects
- And Create gitlab user "John"
+ And User "John Doe" exists
And I visit admin groups page
Scenario: See group list
@@ -17,5 +17,5 @@ Feature: Admin Groups
@javascript
Scenario: Add user into projects in group
When I visit admin group page
- When I select user "John" from user list as "Reporter"
- Then I should see "John" in team list in every project as "Reporter"
+ When I select user "John Doe" from user list as "Reporter"
+ Then I should see "John Doe" in team list in every project as "Reporter"
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 7f503cf923..ce9f32f50d 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -14,3 +14,9 @@ Feature: Admin Users
And Click save
Then See username error message
And Not changed form action url
+
+ Scenario: Edit my user attributes
+ Given I visit admin users page
+ And click edit on my user
+ When I submit modified user
+ Then I see user attributes changed
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
new file mode 100644
index 0000000000..399c9b53d8
--- /dev/null
+++ b/features/dashboard/archived_projects.feature
@@ -0,0 +1,16 @@
+Feature: Dashboard with archived projects
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I own project "Forum"
+ And project "Forum" is archived
+ And I visit dashboard page
+
+ Scenario: I should see non-archived projects on dashboard
+ Then I should see "Shop" project link
+ And I should not see "Forum" project link
+
+ Scenario: I should see all projects on projects page
+ And I visit dashboard projects page
+ Then I should see "Shop" project link
+ And I should see "Forum" project link
diff --git a/features/dashboard/help.feature b/features/dashboard/help.feature
new file mode 100644
index 0000000000..02ec688f80
--- /dev/null
+++ b/features/dashboard/help.feature
@@ -0,0 +1,8 @@
+Feature: Help
+ Background:
+ Given I sign in as a user
+ And I visit the "Rake Tasks" help page
+
+ Scenario: The markdown should be rendered correctly
+ Then I should see "Rake Tasks" page markdown rendered
+ And Header "Rebuild project satellites" should have correct ids and links
diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature
index 895b89aa38..d316b2d920 100644
--- a/features/dashboard/issues.feature
+++ b/features/dashboard/issues.feature
@@ -1,8 +1,18 @@
Feature: Dashboard Issues
Background:
Given I sign in as a user
+ And I have authored issues
And I have assigned issues
+ And I have other issues
And I visit dashboard issues page
- Scenario: I should see issues list
+ Scenario: I should see assigned issues
Then I should see issues assigned to me
+
+ Scenario: I should see authored issues
+ When I click "Authored by me" link
+ Then I should see issues authored by me
+
+ Scenario: I should see all issues
+ When I click "All" link
+ Then I should see all issues
diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature
index cad65b0d79..de56030073 100644
--- a/features/dashboard/merge_requests.feature
+++ b/features/dashboard/merge_requests.feature
@@ -2,7 +2,17 @@ Feature: Dashboard Merge Requests
Background:
Given I sign in as a user
And I have authored merge requests
+ And I have assigned merge requests
+ And I have other merge requests
And I visit dashboard merge requests page
- Scenario: I should see projects list
- Then I should see my merge requests
+ Scenario: I should see assigned merge_requests
+ Then I should see merge requests assigned to me
+
+ Scenario: I should see authored merge_requests
+ When I click "Authored by me" link
+ Then I should see merge requests authored by me
+
+ Scenario: I should see all merge_requests
+ When I click "All" link
+ Then I should see all merge requests
diff --git a/features/group.feature b/features/group.feature
new file mode 100644
index 0000000000..4e11bcba93
--- /dev/null
+++ b/features/group.feature
@@ -0,0 +1,115 @@
+Feature: Groups
+ Background:
+ Given I sign in as "John Doe"
+ And "John Doe" is owner of group "Owned"
+ And "John Doe" is guest of group "Guest"
+
+ @javascript
+ Scenario: I should see group "Owned" dashboard list
+ When I visit group "Owned" page
+ Then I should see group "Owned" projects list
+ And I should see projects activity feed
+
+ Scenario: Create a group from dasboard
+ When I visit group "Owned" page
+ And I visit dashboard page
+ And I click new group link
+ And submit form with new group "Samurai" info
+ Then I should be redirected to group "Samurai" page
+ And I should see newly created group "Samurai"
+
+ Scenario: I should see group "Owned" issues list
+ Given project from group "Owned" has issues assigned to me
+ When I visit group "Owned" issues page
+ Then I should see issues from group "Owned" assigned to me
+
+ Scenario: I should see group "Owned" merge requests list
+ Given project from group "Owned" has merge requests assigned to me
+ When I visit group "Owned" merge requests page
+ Then I should see merge requests from group "Owned" assigned to me
+
+ @javascript
+ Scenario: I should add user to projects in group "Owned"
+ Given User "Mary Jane" exists
+ When I visit group "Owned" members page
+ And I select user "Mary Jane" from list with role "Reporter"
+ Then I should see user "Mary Jane" in team list
+
+ Scenario: I should see edit group "Owned" page
+ When I visit group "Owned" settings page
+ And I change group "Owned" name to "new-name"
+ Then I should see new group "Owned" name
+
+ Scenario: I edit group "Owned" avatar
+ When I visit group "Owned" settings page
+ And I change group "Owned" avatar
+ And I visit group "Owned" settings page
+ Then I should see new group "Owned" avatar
+ And I should see the "Remove avatar" button
+
+ Scenario: I remove group "Owned" avatar
+ When I visit group "Owned" settings page
+ And I have group "Owned" avatar
+ And I visit group "Owned" settings page
+ And I remove group "Owned" avatar
+ Then I should not see group "Owned" avatar
+ And I should not see the "Remove avatar" button
+
+ # Leave
+
+ @javascript
+ Scenario: Owner should be able to remove himself from group if he is not the last owner
+ Given "Mary Jane" is owner of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ And I visit group "Owned" members page
+ Then I should not see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+
+ @javascript
+ Scenario: Owner should not be able to remove himself from group if he is the last owner
+ Given "Mary Jane" is guest of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ Then I should not see the "Remove User From Group" button for "John Doe"
+
+ @javascript
+ Scenario: Guest should be able to remove himself from group
+ Given "Mary Jane" is guest of group "Guest"
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ When I visit group "Guest" members page
+ Then I should not see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+
+ @javascript
+ Scenario: Guest should be able to remove himself from group even if he is the only user in the group
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ When I visit group "Guest" members page
+ Then I should not see user "John Doe" in team list
+
+ # Remove others
+
+ Scenario: Owner should be able to remove other users from group
+ Given "Mary Jane" is owner of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "Mary Jane"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should not see user "Mary Jane" in team list
+
+ Scenario: Guest should not be able to remove other users from group
+ Given "Mary Jane" is guest of group "Guest"
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ Then I should not see the "Remove User From Group" button for "Mary Jane"
diff --git a/features/group/create_group.feature b/features/group/create_group.feature
deleted file mode 100644
index b77f3599e6..0000000000
--- a/features/group/create_group.feature
+++ /dev/null
@@ -1,11 +0,0 @@
-Feature: Groups
- Background:
- Given I sign in as a user
-
- Scenario: Create a group from dasboard
- Given I have group with projects
- And I visit dashboard page
- When I click new group link
- And submit form with new group info
- Then I should be redirected to group page
- And I should see newly created group
diff --git a/features/group/group.feature b/features/group/group.feature
deleted file mode 100644
index 9fec19a4dc..0000000000
--- a/features/group/group.feature
+++ /dev/null
@@ -1,33 +0,0 @@
-Feature: Groups
- Background:
- Given I sign in as a user
- And I have group with projects
-
- @javascript
- Scenario: I should see group dashboard list
- When I visit group page
- Then I should see projects list
- And I should see projects activity feed
-
- Scenario: I should see group issues list
- Given project from group has issues assigned to me
- When I visit group issues page
- Then I should see issues from this group assigned to me
-
- Scenario: I should see group merge requests list
- Given project from group has merge requests assigned to me
- When I visit group merge requests page
- Then I should see merge requests from this group assigned to me
-
- @javascript
- Scenario: I should add user to projects in Group
- Given I have new user "John"
- When I visit group members page
- And I select user "John" from list with role "Reporter"
- Then I should see user "John" in team list
-
- Scenario: I should see edit group page
- When I visit group settings page
- And I change group name
- Then I should see new group name
-
diff --git a/features/profile/emails.feature b/features/profile/emails.feature
new file mode 100644
index 0000000000..148fc76608
--- /dev/null
+++ b/features/profile/emails.feature
@@ -0,0 +1,25 @@
+Feature: Profile Emails
+ Background:
+ Given I sign in as a user
+ And I visit profile emails page
+
+ Scenario: I should see emails
+ Then I should see my emails
+
+ Scenario: Add new email
+ Given I submit new email "my@email.com"
+ Then I should see new email "my@email.com"
+ And I should see my emails
+
+ Scenario: Add duplicate email
+ Given I submit duplicate email @user.email
+ Then I should not have @user.email added
+ And I should see my emails
+
+ Scenario: Remove email
+ Given I submit new email "my@email.com"
+ Then I should see new email "my@email.com"
+ And I should see my emails
+ Then I click link "Remove" for "my@email.com"
+ Then I should not see email "my@email.com"
+ And I should see my emails
diff --git a/features/profile/group.feature b/features/profile/group.feature
new file mode 100644
index 0000000000..70b682e291
--- /dev/null
+++ b/features/profile/group.feature
@@ -0,0 +1,47 @@
+Feature: Profile Group
+ Background:
+ Given I sign in as "John Doe"
+ And "John Doe" is owner of group "Owned"
+ And "John Doe" is guest of group "Guest"
+
+ # Leave groups
+
+ @javascript
+ Scenario: Owner should be able to leave from group if he is not the last owner
+ Given "Mary Jane" is owner of group "Owned"
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should see group "Guest" in group list
+ When I click on the "Leave" button for group "Owned"
+ And I visit profile groups page
+ Then I should not see group "Owned" in group list
+ Then I should see group "Guest" in group list
+
+ @javascript
+ Scenario: Owner should not be able to leave from group if he is the last owner
+ Given "Mary Jane" is guest of group "Owned"
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should see group "Guest" in group list
+ Then I should not see the "Leave" button for group "Owned"
+
+ @javascript
+ Scenario: Guest should be able to leave from group
+ Given "Mary Jane" is guest of group "Guest"
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should see group "Guest" in group list
+ When I click on the "Leave" button for group "Guest"
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should not see group "Guest" in group list
+
+ @javascript
+ Scenario: Guest should be able to leave from group even if he is the only user in the group
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should see group "Guest" in group list
+ When I click on the "Leave" button for group "Guest"
+ When I visit profile groups page
+ Then I should see group "Owned" in group list
+ Then I should not see group "Guest" in group list
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index 6198fd2b30..44bb190f62 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -6,10 +6,17 @@ Feature: Profile
Given I visit profile page
Then I should see my profile info
+ Scenario: I can see groups I belong to
+ Given I have group with projects
+ When I visit profile page
+ And I click on my profile picture
+ Then I should see my user page
+ And I should see groups I belong to
+
Scenario: I edit profile
Given I visit profile page
- Then I change my contact info
- And I should see new contact info
+ Then I change my profile info
+ And I should see new profile info
Scenario: I change my password without old one
Given I visit profile password page
@@ -26,6 +33,14 @@ Feature: Profile
Given I visit profile page
Then I change my avatar
And I should see new avatar
+ And I should see the "Remove avatar" button
+
+ Scenario: I remove my avatar
+ Given I visit profile page
+ And I have an avatar
+ When I remove my avatar
+ Then I should see my gravatar
+ And I should not see the "Remove avatar" button
Scenario: My password is expired
Given my password is expired
diff --git a/features/project/archived.feature b/features/project/archived.feature
new file mode 100644
index 0000000000..9aac29384b
--- /dev/null
+++ b/features/project/archived.feature
@@ -0,0 +1,39 @@
+Feature: Project Archived
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I own project "Forum"
+
+ Scenario: I should not see archived on project page of not-archive project
+ And project "Forum" is archived
+ And I visit project "Shop" page
+ Then I should not see "Archived"
+
+ Scenario: I should see archived on project page of archive project
+ And project "Forum" is archived
+ And I visit project "Forum" page
+ Then I should see "Archived"
+
+ Scenario: I should not see archived on projects page with no archived projects
+ And I visit dashboard projects page
+ Then I should not see "Archived"
+
+ Scenario: I should see archived on projects page with archived projects
+ And project "Forum" is archived
+ And I visit dashboard projects page
+ Then I should see "Archived"
+
+ Scenario: I archive project
+ When project "Shop" has push event
+ And I visit project "Shop" page
+ And I visit edit project "Shop" page
+ And I set project archived
+ Then I should see "Archived"
+
+ Scenario: I unarchive project
+ When project "Shop" has push event
+ And project "Shop" is archived
+ And I visit project "Shop" page
+ And I visit edit project "Shop" page
+ And I set project unarchived
+ Then I should not see "Archived"
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index 4fa4dc26a1..fcf8b7694f 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -16,11 +16,7 @@ Feature: Project Browse branches
Given I click link "Protected"
Then I should see "Shop" protected branches list
- # @wip
- # Scenario: I can download project by branch
-
- # @wip
- # Scenario: I can view protected branches
-
- # @wip
- # Scenario: I can manage protected branches
+ Scenario: I create a branch
+ Given I click new branch link
+ When I submit new branch form
+ Then I should see new branch created
diff --git a/features/project/commits/commit_comments.feature b/features/project/commits/comments.feature
similarity index 100%
rename from features/project/commits/commit_comments.feature
rename to features/project/commits/comments.feature
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index fe470f5ac9..cbe8b32150 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -14,6 +14,12 @@ Feature: Project Browse commits
Scenario: I browse commit from list
Given I click on commit link
Then I see commit info
+ And I see side-by-side diff button
+
+ Scenario: I browse commit with side-by-side diff view
+ Given I click on commit link
+ And I click side-by-side diff button
+ Then I see inline diff button
Scenario: I compare refs
Given I visit compare refs page
diff --git a/features/project/commits/commit_diff_comments.feature b/features/project/commits/diff_comments.feature
similarity index 100%
rename from features/project/commits/commit_diff_comments.feature
rename to features/project/commits/diff_comments.feature
diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature
new file mode 100644
index 0000000000..f3864c0ab3
--- /dev/null
+++ b/features/project/commits/user_lookup.feature
@@ -0,0 +1,14 @@
+Feature: Project Browse Commits User Lookup
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And I have the user that authored the commits
+ And I visit my project's commits page
+
+ Scenario: I browse commit from list
+ Given I click on commit link
+ Then I see commit info
+
+ Scenario: I browse another commit from list
+ Given I click on another commit link
+ Then I see other commit info
\ No newline at end of file
diff --git a/features/project/create_project.feature b/features/project/create.feature
similarity index 91%
rename from features/project/create_project.feature
rename to features/project/create.feature
index 395a3218b2..bb8e3a368e 100644
--- a/features/project/create_project.feature
+++ b/features/project/create.feature
@@ -3,6 +3,7 @@ Feature: Create Project
A user with ability to create a project
Should be able to create a new one
+ @javascript
Scenario: User create a project
Given I sign in as a user
When I visit new project page
@@ -19,4 +20,4 @@ Feature: Create Project
And I click on HTTP
Then Remote url should update to http link
And If I click on SSH
- Then Remote url should update to ssh link
\ No newline at end of file
+ Then Remote url should update to ssh link
diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature
new file mode 100644
index 0000000000..b5477d3c7a
--- /dev/null
+++ b/features/project/edit_issuetracker.feature
@@ -0,0 +1,18 @@
+Feature: Project Issue Tracker
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has issues enabled
+ And I visit project "Shop" page
+
+ Scenario: I set the issue tracker to "GitLab"
+ When I visit edit project "Shop" page
+ And change the issue tracker to "GitLab"
+ And I save project
+ Then I the project should have "GitLab" as issue tracker
+
+ Scenario: I set the issue tracker to "Redmine"
+ When I visit edit project "Shop" page
+ And change the issue tracker to "Redmine"
+ And I save project
+ Then I the project should have "Redmine" as issue tracker
\ No newline at end of file
diff --git a/features/project/fork_project.feature b/features/project/fork.feature
similarity index 100%
rename from features/project/fork_project.feature
rename to features/project/fork.feature
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index 966905645a..5832b729de 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -30,5 +30,10 @@ Feature: Project Forked Merge Requests
Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
And I fill out an invalid "Merge Request On Forked Project" merge request
- And I submit the merge request
Then I should see validation errors
+
+ @javascript
+ Scenario: Merge request should target fork repository by default
+ Given I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ Then the target repository should be the original repository
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
new file mode 100644
index 0000000000..8df7a119e8
--- /dev/null
+++ b/features/project/issues/filter_labels.feature
@@ -0,0 +1,26 @@
+Feature: Project Filter Labels
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has issue "Bugfix1" with tags: "bug", "feature"
+ And project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"
+ And project "Shop" has issue "Feature1" with tags: "feature"
+ Given I visit project "Shop" issues page
+
+ Scenario: I should see project issues
+ Then I should see "bug" in labels filter
+ And I should see "feature" in labels filter
+ And I should see "enhancement" in labels filter
+
+ Scenario: I filter by one label
+ Given I click link "bug"
+ Then I should see "Bugfix1" in issues list
+ And I should see "Bugfix2" in issues list
+ And I should not see "Feature1" in issues list
+
+ Scenario: I filter by two labels
+ Given I click link "bug"
+ And I click link "feature"
+ Then I should see "Bugfix1" in issues list
+ And I should not see "Bugfix2" in issues list
+ And I should not see "Feature1" in issues list
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 67986784bc..c5311544ef 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -55,3 +55,24 @@ Feature: Project Issues
And I fill in issue search with ".3"
Then I should see "Release 0.3" in issues
And I should not see "Release 0.4" in issues
+
+ # Markdown
+
+ Scenario: Headers inside the description should have ids generated for them.
+ Given I visit issue page "Release 0.4"
+ Then Header "Description header" should have correct id and link
+
+ @javascript
+ Scenario: Headers inside comments should not have ids generated for them.
+ Given I visit issue page "Release 0.4"
+ And I leave a comment with a header containing "Comment with a header"
+ Then The comment with the header should not have an ID
+
+ Scenario: Issues on empty project
+ Given empty project "Empty Project"
+ When I visit empty project page
+ And I see empty project details with ssh clone info
+ When I visit empty project's issues page
+ Given I click link "New Issue"
+ And I submit new issue "500 error on profile"
+ Then I should see issue "500 error on profile"
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index 2f38acf14d..e67b5d2d86 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -22,3 +22,9 @@ Feature: Project Milestones
Given the milestone has open and closed issues
And I click link "v2.2"
Then I should see 3 issues
+
+ # Markdown
+
+ Scenario: Headers inside the description should have ids generated for them.
+ Given I click link "v2.2"
+ Then Header "Description header" should have correct id and link
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 63f27c3acc..6a2ba7e3b2 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -29,7 +29,13 @@ Feature: Project Merge Requests
And I click link "Close"
Then I should see closed merge request "Bug NS-04"
- @javascript
+ Scenario: I reopen merge request page
+ Given I click link "Bug NS-04"
+ And I click link "Close"
+ Then I should see closed merge request "Bug NS-04"
+ When I click link "Reopen"
+ Then I should see reopened merge request "Bug NS-04"
+
Scenario: I submit new unassigned merge request
Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature"
@@ -55,15 +61,81 @@ Feature: Project Merge Requests
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the first commit in the merge request
- And I leave a comment like "Line is wrong" on line 185 of the first file
+ And I leave a comment like "Line is wrong" on line 185 of the first file in commit
And I switch to the merge request's comments tab
- Then I should see a discussion has started on commit bcf03b5de6c:L185
+ Then I should see a discussion has started on commit b1e6a9dbf1:L185
@javascript
Scenario: I comment on a commit in merge request
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the first commit in the merge request
- And I leave a comment on the diff page
+ And I leave a comment on the diff page in commit
And I switch to the merge request's comments tab
- Then I should see a discussion has started on commit bcf03b5de6c
+ Then I should see a discussion has started on commit b1e6a9dbf1
+
+ @javascript
+ Scenario: I accept merge request with custom commit message
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And merge request "Bug NS-05" is mergeable
+ And I visit merge request page "Bug NS-05"
+ And merge request is mergeable
+ Then I modify merge commit message
+ And I accept this merge request
+ Then I should see merged request
+
+ # Markdown
+
+ Scenario: Headers inside the description should have ids generated for them.
+ When I visit merge request page "Bug NS-04"
+ Then Header "Description header" should have correct id and link
+
+ @javascript
+ Scenario: Headers inside comments should not have ids generated for them.
+ Given I visit merge request page "Bug NS-04"
+ And I leave a comment with a header containing "Comment with a header"
+ Then The comment with the header should not have an ID
+
+ # Toggling inline comments
+
+ @javascript
+ Scenario: I hide comments on a merge request diff with comments in a single file
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ Then I should not see a comment like "Line is wrong" in the second file
+
+ @javascript
+ Scenario: I show comments on a merge request diff with comments in a single file
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ And I click link "Show inline discussion" of the second file
+ Then I should see a comment like "Line is wrong" in the second file
+
+ @javascript
+ Scenario: I hide comments on a merge request diff with comments in multiple files
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is correct" on line 12 of the first file
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ Then I should not see a comment like "Line is wrong" in the second file
+ And I should still see a comment like "Line is correct" in the first file
+
+ @javascript
+ Scenario: I show comments on a merge request diff with comments in multiple files
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I switch to the diff tab
+ And I leave a comment like "Line is correct" on line 12 of the first file
+ And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I click link "Hide inline discussion" of the second file
+ And I click link "Show inline discussion" of the second file
+ Then I should see a comment like "Line is wrong" in the second file
+ And I should still see a comment like "Line is correct" in the first file
diff --git a/features/project/network.feature b/features/project/network.feature
index ceae08c107..22beb1c50b 100644
--- a/features/project/network.feature
+++ b/features/project/network.feature
@@ -29,11 +29,11 @@ Feature: Project Network Graph
@javascript
Scenario: I should filter selected tag
When I switch ref to "v2.1.0"
- Then page should have content not cotaining "v2.1.0"
+ Then page should have content not containing "v2.1.0"
When click "Show only selected branch" checkbox
- Then page should not have content not cotaining "v2.1.0"
+ Then page should not have content not containing "v2.1.0"
When click "Show only selected branch" checkbox
- Then page should have content not cotaining "v2.1.0"
+ Then page should have content not containing "v2.1.0"
Scenario: I should fail to look for a commit
When I look for a commit by ";"
diff --git a/features/project/project.feature b/features/project/project.feature
index 59eda4a781..d8bb1d55e2 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -19,3 +19,8 @@ Feature: Project Feature
And change project settings
And I save project
Then I should see project with new settings
+
+ Scenario: I change project path
+ When I visit edit project "Shop" page
+ And change project path settings
+ Then I should see project with new path settings
diff --git a/features/project/public_projects.feature b/features/project/public.feature
similarity index 100%
rename from features/project/public_projects.feature
rename to features/project/public.feature
diff --git a/features/project/redirects.feature b/features/project/redirects.feature
new file mode 100644
index 0000000000..ce197912f6
--- /dev/null
+++ b/features/project/redirects.feature
@@ -0,0 +1,26 @@
+Feature: Project Redirects
+ Background:
+ Given public project "Community"
+ And private project "Enterprise"
+
+ Scenario: I visit public project page
+ When I visit project "Community" page
+ Then I should see project "Community" home page
+
+ Scenario: I visit private project page
+ When I visit project "Enterprise" page
+ Then I should be redirected to sign in page
+
+ Scenario: I visit a non-existent project page
+ When I visit project "CommunityDoesNotExist" page
+ Then I should be redirected to sign in page
+
+ Scenario: I visit a non-existent project page as user
+ Given I sign in as a user
+ When I visit project "CommunityDoesNotExist" page
+ Then page status code should be 404
+
+ Scenario: I visit unauthorized project page as user
+ Given I sign in as a user
+ When I visit project "Enterprise" page
+ Then page status code should be 404
diff --git a/features/project/service.feature b/features/project/service.feature
index 4805d2befb..a5af065c9e 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -30,3 +30,21 @@ Feature: Project Services
And I click Flowdock service link
And I fill Flowdock settings
Then I should see Flowdock service settings saved
+
+ Scenario: Activate Assembla service
+ When I visit project "Shop" services page
+ And I click Assembla service link
+ And I fill Assembla settings
+ Then I should see Assembla service settings saved
+
+ Scenario: Activate Slack service
+ When I visit project "Shop" services page
+ And I click Slack service link
+ And I fill Slack settings
+ Then I should see Slack service settings saved
+
+ Scenario: Activate email on push service
+ When I visit project "Shop" services page
+ And I click email on push service link
+ And I fill email on push settings
+ Then I should see email on push service settings saved
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index ee26f5371a..a204c3e10c 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -20,8 +20,22 @@ Feature: Project Browse files
And I click link "raw"
Then I should see raw file content
+ Scenario: I can create file
+ Given I click on "new file" link in repo
+ Then I can see new file page
+
@javascript
Scenario: I can edit file
Given I click on "Gemfile.lock" file in repo
And I click button "edit"
Then I can edit code
+
+ @javascript
+ Scenario: I can see editing preview
+ Given I click on "Gemfile.lock" file in repo
+ And I click button "edit"
+ And I edit code
+ And I click link "Diff"
+ Then I see diff
+
+
diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature
index 8b4b89e85a..970a9e5786 100644
--- a/features/project/source/markdown_render.feature
+++ b/features/project/source/markdown_render.feature
@@ -4,6 +4,13 @@ Feature: Project markdown render
And I own project "Delta"
Given I visit project source page
+ # Tree README
+
+ Scenario: Tree view should have correct links in README
+ Given I go directory which contains README file
+ And I click on a relative link in README
+ Then I should see the correct markdown
+
Scenario: I browse files from master branch
Then I should see files from repository in master
And I should see rendered README which contains correct links
@@ -16,6 +23,24 @@ Feature: Project markdown render
And I click on Rake tasks in README
Then I should see correct directory rendered
+ Scenario: I view README in master branch to see reference links to directory
+ Then I should see files from repository in master
+ And I should see rendered README which contains correct links
+ And I click on GitLab API doc directory in README
+ Then I should see correct doc/api directory rendered
+
+ Scenario: I view README in master branch to see reference links to file
+ Then I should see files from repository in master
+ And I should see rendered README which contains correct links
+ And I click on Maintenance in README
+ Then I should see correct maintenance file rendered
+
+ Scenario: README headers should have header links
+ Then I should see rendered README which contains correct links
+ And Header "Application details" should have correct id and link
+
+ # Blob
+
Scenario: I navigate to doc directory to view documentation in master
And I navigate to the doc/api/README
And I see correct file rendered
@@ -28,6 +53,12 @@ Feature: Project markdown render
And I click on raketasks in doc/api/README
Then I should see correct directory rendered
+ Scenario: I navigate to doc directory to view user doc in master
+ And I navigate to the doc/api/README
+ And Header "GitLab API" should have correct id and link
+
+ # Markdown branch
+
Scenario: I browse files from markdown branch
When I visit markdown branch
Then I should see files from repository in markdown branch
@@ -56,6 +87,32 @@ Feature: Project markdown render
And I click on raketasks in doc/api/README
Then I should see correct directory rendered for markdown branch
+ Scenario: Tree markdown links view empty urls should have correct urls
+ When I visit markdown branch
+ Then The link with text "empty" should have url "tree/markdown"
+ When I visit markdown branch "README.md" blob
+ Then The link with text "empty" should have url "blob/markdown/README.md"
+ When I visit markdown branch "d" tree
+ Then The link with text "empty" should have url "tree/markdown/d"
+ When I visit markdown branch "d/README.md" blob
+ Then The link with text "empty" should have url "blob/markdown/d/README.md"
+
+ # "ID" means "#id" on the tests below, because we are unable to escape the hash sign.
+ # which Spinach interprets as the start of a comment.
+ Scenario: All markdown links with ids should have correct urls
+ When I visit markdown branch
+ Then The link with text "ID" should have url "tree/markdownID"
+ Then The link with text "/ID" should have url "tree/markdownID"
+ Then The link with text "README.mdID" should have url "blob/markdown/README.mdID"
+ Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"
+ When I visit markdown branch "README.md" blob
+ Then The link with text "ID" should have url "blob/markdown/README.mdID"
+ Then The link with text "/ID" should have url "blob/markdown/README.mdID"
+ Then The link with text "README.mdID" should have url "blob/markdown/README.mdID"
+ Then The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"
+
+ # Wiki
+
Scenario: I create a wiki page with different links
Given I go to wiki page
And I add various links to the wiki page
@@ -69,12 +126,7 @@ Feature: Project markdown render
And I click on Rake tasks link
Then I see Rake tasks directory
- Scenario: I visit the help page with markdown
- Given I visit to the help page
- And I select a page with markdown
- Then I should see a help page with markdown
-
- Scenario: Tree view should have correct links in README
- Given I go directory which contains README file
- And I click on a relative link in README
- Then I should see the correct markdown
+ Scenario: Wiki headers should have should have ids generated for them.
+ Given I go to wiki page
+ And I add a header to the wiki page
+ Then Wiki header should have correct id and link
diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature
new file mode 100644
index 0000000000..3038c0814a
--- /dev/null
+++ b/features/project/source/multiselect_blob.feature
@@ -0,0 +1,86 @@
+Feature: Project Multiselect Blob
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit project source page
+ And I click on "Gemfile.lock" file in repo
+
+ @javascript
+ Scenario: I click line 1 in file
+ When I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I shift-click line 1 in file
+ When I shift-click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I click line 1 then click line 2 in file
+ When I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I click line 2 in file
+ Then I should see "L2" as URI fragment
+ And I should see line 2 highlighted
+
+ @javascript
+ Scenario: I click various line numbers to test multiselect
+ Then I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I shift-click line 2 in file
+ Then I should see "L1-2" as URI fragment
+ And I should see lines 1-2 highlighted
+ Then I shift-click line 3 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I click line 3 in file
+ Then I should see "L3" as URI fragment
+ And I should see line 3 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I shift-click line 5 in file
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted
+ Then I shift-click line 4 in file
+ Then I should see "L1-4" as URI fragment
+ And I should see lines 1-4 highlighted
+ Then I click line 5 in file
+ Then I should see "L5" as URI fragment
+ And I should see line 5 highlighted
+ Then I shift-click line 3 in file
+ Then I should see "L3-5" as URI fragment
+ And I should see lines 3-5 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I multiselect lines 1-5 and then go back and forward in history
+ When I click line 1 in file
+ And I shift-click line 3 in file
+ And I shift-click line 2 in file
+ And I shift-click line 5 in file
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted
+ Then I go back in history
+ Then I should see "L1-2" as URI fragment
+ And I should see lines 1-2 highlighted
+ Then I go back in history
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I go back in history
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I go forward in history
+ And I go forward in history
+ And I go forward in history
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted
\ No newline at end of file
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index 90eb2b79c6..4a8c771dda 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -45,3 +45,20 @@ Feature: Project Wiki
And I browse to that Wiki page
And I click on the "Pages" button
Then I should see the existing page in the pages list
+
+ Scenario: File exists in wiki repo
+ Given I have an existing Wiki page with images linked on page
+ And I browse to wiki page with images
+ And I click on existing image link
+ Then I should see the image from wiki repo
+
+ Scenario: Image in wiki repo shown on the page
+ Given I have an existing Wiki page with images linked on page
+ And I browse to wiki page with images
+ Then Image should be shown on the page
+
+ Scenario: File does not exist in wiki repo
+ Given I have an existing Wiki page with images linked on page
+ And I browse to wiki page with images
+ And I click on image link
+ Then I should see the new wiki page form
diff --git a/features/public/projects.feature b/features/public/projects.feature
new file mode 100644
index 0000000000..57fe834b4b
--- /dev/null
+++ b/features/public/projects.feature
@@ -0,0 +1,101 @@
+Feature: Public Projects Feature
+ Background:
+ Given public project "Community"
+ And internal project "Internal"
+ And private project "Enterprise"
+
+ Scenario: I visit public area
+ When I visit the public projects area
+ Then I should see project "Community"
+ And I should not see project "Internal"
+ And I should not see project "Enterprise"
+
+ Scenario: I visit public project page
+ When I visit project "Community" page
+ Then I should see project "Community" home page
+
+ Scenario: I visit internal project page
+ When I visit project "Internal" page
+ Then I should be redirected to sign in page
+
+ Scenario: I visit private project page
+ When I visit project "Enterprise" page
+ Then I should be redirected to sign in page
+
+ Scenario: I visit an empty public project page
+ Given public empty project "Empty Public Project"
+ When I visit empty project page
+ Then I should see empty public project details
+ And I should see empty public project details with http clone info
+
+ Scenario: I visit an empty public project page as user
+ Given I sign in as a user
+ And public empty project "Empty Public Project"
+ When I visit empty project page
+ Then I should see empty public project details
+ And I should see empty public project details with ssh clone info
+
+ Scenario: I visit public area as user
+ Given I sign in as a user
+ When I visit the public projects area
+ Then I should see project "Community"
+ And I should see project "Internal"
+ And I should not see project "Enterprise"
+
+ Scenario: I visit internal project page as user
+ Given I sign in as a user
+ When I visit project "Internal" page
+ Then I should see project "Internal" home page
+
+ Scenario: I visit public project page
+ When I visit project "Community" page
+ Then I should see project "Community" home page
+ And I should see an http link to the repository
+
+ Scenario: I visit public project page as user
+ Given I sign in as a user
+ When I visit project "Community" page
+ Then I should see project "Community" home page
+ And I should see an ssh link to the repository
+
+ Scenario: I visit an empty public project page
+ Given public empty project "Empty Public Project"
+ When I visit empty project page
+ Then I should see empty public project details
+
+ Scenario: I visit public project issues page as a non authorized user
+ Given I visit project "Community" page
+ And I visit "Community" issues page
+ Then I should see list of issues for "Community" project
+
+ Scenario: I visit public project issues page as authorized user
+ Given I sign in as a user
+ Given I visit project "Community" page
+ And I visit "Community" issues page
+ Then I should see list of issues for "Community" project
+
+ Scenario: I visit internal project issues page as authorized user
+ Given I sign in as a user
+ Given I visit project "Internal" page
+ And I visit "Internal" issues page
+ Then I should see list of issues for "Internal" project
+
+ Scenario: I visit public project merge requests page as an authorized user
+ Given I sign in as a user
+ Given I visit project "Community" page
+ And I visit "Community" merge requests page
+ And project "Community" has "Bug fix" open merge request
+ Then I should see list of merge requests for "Community" project
+
+ Scenario: I visit public project merge requests page as a non authorized user
+ Given I visit project "Community" page
+ And I visit "Community" merge requests page
+ And project "Community" has "Bug fix" open merge request
+ Then I should see list of merge requests for "Community" project
+
+ Scenario: I visit internal project merge requests page as an authorized user
+ Given I sign in as a user
+ Given I visit project "Internal" page
+ And I visit "Internal" merge requests page
+ And project "Internal" has "Feature implemented" open merge request
+ Then I should see list of merge requests for "Internal" project
diff --git a/features/public/public_groups.feature b/features/public/public_groups.feature
new file mode 100644
index 0000000000..7f1ec718e3
--- /dev/null
+++ b/features/public/public_groups.feature
@@ -0,0 +1,118 @@
+Feature: Public Projects Feature
+ Background:
+ Given group "TestGroup" has private project "Enterprise"
+
+ Scenario: I should not see group with private projects as visitor
+ When I visit group "TestGroup" page
+ Then I should be redirected to sign in page
+
+ Scenario: I should not see group with private projects group as user
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then page status code should be 404
+
+ Scenario: I should not see group with private and internal projects as visitor
+ Given group "TestGroup" has internal project "Internal"
+ When I visit group "TestGroup" page
+ Then I should be redirected to sign in page
+
+ Scenario: I should see group with private and internal projects as user
+ Given group "TestGroup" has internal project "Internal"
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for internal project as user
+ Given group "TestGroup" has internal project "Internal"
+ When I sign in as a user
+ And I visit group "TestGroup" issues page
+ And I change filter to Everyone's
+ Then I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group merge requests for internal project as user
+ Given group "TestGroup" has internal project "Internal"
+ When I sign in as a user
+ And I visit group "TestGroup" merge requests page
+ And I change filter to Everyone's
+ Then I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group's members as user
+ Given group "TestGroup" has internal project "Internal"
+ And "John Doe" is owner of group "TestGroup"
+ When I sign in as a user
+ And I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
+
+ Scenario: I should see group with private, internal and public projects as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for public project as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" issues page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group merge requests for public project as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I visit group "TestGroup" merge requests page
+ Then I should see project "Community" items
+ And I should not see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group's members as visitor
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ And "John Doe" is owner of group "TestGroup"
+ When I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
+
+ Scenario: I should see group with private, internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I sign in as a user
+ And I visit group "TestGroup" page
+ Then I should see project "Community" items
+ And I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group issues for internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I sign in as a user
+ And I visit group "TestGroup" issues page
+ And I change filter to Everyone's
+ Then I should see project "Community" items
+ And I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group merge requests for internal and public projects as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ When I sign in as a user
+ And I visit group "TestGroup" merge requests page
+ And I change filter to Everyone's
+ Then I should see project "Community" items
+ And I should see project "Internal" items
+ And I should not see project "Enterprise" items
+
+ Scenario: I should see group's members as user
+ Given group "TestGroup" has internal project "Internal"
+ Given group "TestGroup" has public project "Community"
+ And "John Doe" is owner of group "TestGroup"
+ When I sign in as a user
+ And I visit group "TestGroup" members page
+ Then I should see group member "John Doe"
+ And I should not see member roles
diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature
deleted file mode 100644
index 178a769194..0000000000
--- a/features/public/public_projects.feature
+++ /dev/null
@@ -1,18 +0,0 @@
-Feature: Public Projects Feature
- Background:
- Given public project "Community"
- And private project "Enterprise"
-
- Scenario: I visit public area
- When I visit the public projects area
- Then I should see project "Community"
- And I should not see project "Enterprise"
-
- Scenario: I visit public project page
- When I visit project "Community" page
- Then I should see project "Community" home page
-
- Scenario: I visit an empty public project page
- Given public empty project "Empty Public Project"
- When I visit empty project page
- Then I should see empty public project details
diff --git a/features/snippets/discover_snippets.feature b/features/snippets/discover.feature
similarity index 100%
rename from features/snippets/discover_snippets.feature
rename to features/snippets/discover.feature
diff --git a/features/snippets/user_snippets.feature b/features/snippets/user.feature
similarity index 100%
rename from features/snippets/user_snippets.feature
rename to features/snippets/user.feature
diff --git a/features/steps/admin/admin_active_tab.rb b/features/steps/admin/active_tab.rb
similarity index 88%
rename from features/steps/admin/admin_active_tab.rb
rename to features/steps/admin/active_tab.rb
index f14c5f396b..ccafe09c18 100644
--- a/features/steps/admin/admin_active_tab.rb
+++ b/features/steps/admin/active_tab.rb
@@ -30,4 +30,8 @@ class AdminActiveTab < Spinach::FeatureSteps
Then 'the active main tab should be Resque' do
ensure_active_main_tab('Background Jobs')
end
+
+ Then 'the active main tab should be Messages' do
+ ensure_active_main_tab('Messages')
+ end
end
diff --git a/features/steps/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb
new file mode 100644
index 0000000000..a35fa34a3a
--- /dev/null
+++ b/features/steps/admin/broadcast_messages.rb
@@ -0,0 +1,41 @@
+class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'application already has admin messages' do
+ FactoryGirl.create(:broadcast_message, message: "Migration to new server")
+ end
+
+ step 'I should be all broadcast messages' do
+ page.should have_content "Migration to new server"
+ end
+
+ step 'submit form with new broadcast message' do
+ fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST'
+ select '2018', from: "broadcast_message_ends_at_1i"
+ click_button "Add broadcast message"
+ end
+
+ step 'I should be redirected to admin messages page' do
+ current_path.should == admin_broadcast_messages_path
+ end
+
+ step 'I should see newly created broadcast message' do
+ page.should have_content 'Application update from 4:00 CST to 5:00 CST'
+ end
+
+ step 'submit form with new customized broadcast message' do
+ fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST'
+ click_link "Customize colors"
+ fill_in 'broadcast_message_color', with: '#f2dede'
+ fill_in 'broadcast_message_font', with: '#b94a48'
+ select '2018', from: "broadcast_message_ends_at_1i"
+ click_button "Add broadcast message"
+ end
+
+ step 'I should see a customized broadcast message' do
+ page.should have_content 'Application update from 4:00 CST to 5:00 CST'
+ page.should have_selector %(div[style="background-color:#f2dede;color:#b94a48"])
+ end
+end
diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/groups.rb
similarity index 83%
rename from features/steps/admin/admin_groups.rb
rename to features/steps/admin/groups.rb
index b4591f227e..9c1bcfefb9 100644
--- a/features/steps/admin/admin_groups.rb
+++ b/features/steps/admin/groups.rb
@@ -1,6 +1,7 @@
class AdminGroups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include SharedUser
include SharedActiveTab
include Select2Helper
@@ -20,10 +21,6 @@ class AdminGroups < Spinach::FeatureSteps
@project.team << [current_user, :master]
end
- And 'Create gitlab user "John"' do
- create(:user, name: "John")
- end
-
And 'submit form with new group info' do
fill_in 'group_name', with: 'gitlab'
fill_in 'group_description', with: 'Group description'
@@ -39,8 +36,8 @@ class AdminGroups < Spinach::FeatureSteps
current_path.should == admin_group_path(Group.last)
end
- When 'I select user "John" from user list as "Reporter"' do
- user = User.find_by_name("John")
+ When 'I select user "John Doe" from user list as "Reporter"' do
+ user = User.find_by(name: "John Doe")
select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
select "Reporter", from: "group_access"
@@ -48,9 +45,9 @@ class AdminGroups < Spinach::FeatureSteps
click_button "Add users into group"
end
- Then 'I should see "John" in team list in every project as "Reporter"' do
+ Then 'I should see "John Doe" in team list in every project as "Reporter"' do
within ".group-users-list" do
- page.should have_content "John"
+ page.should have_content "John Doe"
page.should have_content "Reporter"
end
end
diff --git a/features/steps/admin/admin_logs.rb b/features/steps/admin/logs.rb
similarity index 100%
rename from features/steps/admin/admin_logs.rb
rename to features/steps/admin/logs.rb
diff --git a/features/steps/admin/admin_projects.rb b/features/steps/admin/projects.rb
similarity index 100%
rename from features/steps/admin/admin_projects.rb
rename to features/steps/admin/projects.rb
diff --git a/features/steps/admin/admin_users.rb b/features/steps/admin/users.rb
similarity index 71%
rename from features/steps/admin/admin_users.rb
rename to features/steps/admin/users.rb
index 33c1344eae..659008dd87 100644
--- a/features/steps/admin/admin_users.rb
+++ b/features/steps/admin/users.rb
@@ -31,4 +31,17 @@ class AdminUsers < Spinach::FeatureSteps
And 'Not changed form action url' do
page.should have_selector %(form[action="/admin/users/#{@user.username}"])
end
+
+ step 'I submit modified user' do
+ check :user_can_create_group
+ click_button 'Save'
+ end
+
+ step 'I see user attributes changed' do
+ page.should have_content 'Can create groups: Yes'
+ end
+
+ step 'click edit on my user' do
+ find("#edit_user_#{current_user.id}").click
+ end
end
diff --git a/features/steps/dashboard/dashboard_active_tab.rb b/features/steps/dashboard/active_tab.rb
similarity index 100%
rename from features/steps/dashboard/dashboard_active_tab.rb
rename to features/steps/dashboard/active_tab.rb
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index bde32128b9..706c9babce 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -25,7 +25,6 @@ class Dashboard < Spinach::FeatureSteps
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should == "new_design"
find("#merge_request_target_branch").value.should == "master"
- find("#merge_request_title").value.should == "New Design"
end
Given 'user with name "John Doe" joined project "Shop"' do
@@ -43,7 +42,7 @@ class Dashboard < Spinach::FeatureSteps
end
And 'user with name "John Doe" left project "Shop"' do
- user = User.find_by_name "John Doe"
+ user = User.find_by(name: "John Doe")
Event.create(
project: project,
author_id: user.id,
@@ -85,6 +84,6 @@ class Dashboard < Spinach::FeatureSteps
end
def project
- @project ||= Project.find_by_name "Shop"
+ @project ||= Project.find_by(name: "Shop")
end
end
diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb
deleted file mode 100644
index fcf4296ad1..0000000000
--- a/features/steps/dashboard/dashboard_issues.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-class DashboardIssues < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
-
- Then 'I should see issues assigned to me' do
- issues = @user.issues
- 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
-
- And 'I have assigned issues' do
- project = create :project
- project.team << [@user, :master]
-
- 2.times { create :issue, author: @user, assignee: @user, project: project }
- end
-end
diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/dashboard_merge_requests.rb
deleted file mode 100644
index 6c1fa39f08..0000000000
--- a/features/steps/dashboard/dashboard_merge_requests.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class DashboardMergeRequests < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
-
- Then 'I should see my merge requests' do
- merge_requests = @user.merge_requests
- merge_requests.each do |mr|
- page.should have_content(mr.title[0..10])
- page.should have_content(mr.target_project.name)
- page.should have_content(mr.source_project.name)
- end
- end
-
- And 'I have authored merge requests' do
- project1_source = create :project
- project1_target= create :project
- project2_source = create :project
- project2_target = create :project
-
-
- project1_source.team << [@user, :master]
- project1_target.team << [@user, :master]
- project2_source.team << [@user, :master]
- project2_target.team << [@user, :master]
-
- merge_request1 = create :merge_request, author: @user, source_project: project1_source, target_project: project1_target
- merge_request2 = create :merge_request, author: @user, source_project: project2_source, target_project: project2_target
- end
-end
diff --git a/features/steps/dashboard/dashboard_event_filters.rb b/features/steps/dashboard/event_filters.rb
similarity index 100%
rename from features/steps/dashboard/dashboard_event_filters.rb
rename to features/steps/dashboard/event_filters.rb
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
new file mode 100644
index 0000000000..1344edfa80
--- /dev/null
+++ b/features/steps/dashboard/issues.rb
@@ -0,0 +1,74 @@
+class DashboardIssues < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+
+ step 'I should see issues assigned to me' do
+ should_see(assigned_issue)
+ should_not_see(authored_issue)
+ should_not_see(other_issue)
+ end
+
+ step 'I should see issues authored by me' do
+ should_see(authored_issue)
+ should_not_see(assigned_issue)
+ should_not_see(other_issue)
+ end
+
+ step 'I should see all issues' do
+ should_see(authored_issue)
+ should_see(assigned_issue)
+ should_see(other_issue)
+ end
+
+ step 'I have authored issues' do
+ authored_issue
+ end
+
+ step 'I have assigned issues' do
+ assigned_issue
+ end
+
+ step 'I have other issues' do
+ other_issue
+ end
+
+ step 'I click "Authored by me" link' do
+ within ".scope-filter" do
+ click_link 'Created by me'
+ end
+ end
+
+ step 'I click "All" link' do
+ within ".scope-filter" do
+ click_link "Everyone's"
+ end
+ end
+
+ def should_see(issue)
+ page.should have_content(issue.title[0..10])
+ end
+
+ def should_not_see(issue)
+ page.should_not have_content(issue.title[0..10])
+ end
+
+ def assigned_issue
+ @assigned_issue ||= create :issue, assignee: current_user, project: project
+ end
+
+ def authored_issue
+ @authored_issue ||= create :issue, author: current_user, project: project
+ end
+
+ def other_issue
+ @other_issue ||= create :issue, project: project
+ end
+
+ def project
+ @project ||= begin
+ project =create :project
+ project.team << [current_user, :master]
+ project
+ end
+ end
+end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
new file mode 100644
index 0000000000..e198bc0cf9
--- /dev/null
+++ b/features/steps/dashboard/merge_requests.rb
@@ -0,0 +1,74 @@
+class DashboardMergeRequests < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+
+ step 'I should see merge requests assigned to me' do
+ should_see(assigned_merge_request)
+ should_not_see(authored_merge_request)
+ should_not_see(other_merge_request)
+ end
+
+ step 'I should see merge requests authored by me' do
+ should_see(authored_merge_request)
+ should_not_see(assigned_merge_request)
+ should_not_see(other_merge_request)
+ end
+
+ step 'I should see all merge requests' do
+ should_see(authored_merge_request)
+ should_see(assigned_merge_request)
+ should_see(other_merge_request)
+ end
+
+ step 'I have authored merge requests' do
+ authored_merge_request
+ end
+
+ step 'I have assigned merge requests' do
+ assigned_merge_request
+ end
+
+ step 'I have other merge requests' do
+ other_merge_request
+ end
+
+ step 'I click "Authored by me" link' do
+ within ".scope-filter" do
+ click_link 'Created by me'
+ end
+ end
+
+ step 'I click "All" link' do
+ within ".scope-filter" do
+ click_link "Everyone's"
+ end
+ end
+
+ def should_see(merge_request)
+ page.should have_content(merge_request.title[0..10])
+ end
+
+ def should_not_see(merge_request)
+ page.should_not have_content(merge_request.title[0..10])
+ end
+
+ def assigned_merge_request
+ @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+ end
+
+ def authored_merge_request
+ @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+ end
+
+ def other_merge_request
+ @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+ end
+
+ def project
+ @project ||= begin
+ project =create :project
+ project.team << [current_user, :master]
+ project
+ end
+ end
+end
diff --git a/features/steps/dashboard/dashboard_projects.rb b/features/steps/dashboard/projects.rb
similarity index 100%
rename from features/steps/dashboard/dashboard_projects.rb
rename to features/steps/dashboard/projects.rb
diff --git a/features/steps/dashboard/dashboard_search.rb b/features/steps/dashboard/search.rb
similarity index 100%
rename from features/steps/dashboard/dashboard_search.rb
rename to features/steps/dashboard/search.rb
diff --git a/features/steps/dashboard/with_archived_projects.rb b/features/steps/dashboard/with_archived_projects.rb
new file mode 100644
index 0000000000..1bc69555b5
--- /dev/null
+++ b/features/steps/dashboard/with_archived_projects.rb
@@ -0,0 +1,22 @@
+class DashboardWithArchivedProjects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ When 'project "Forum" is archived' do
+ project = Project.find_by(name: "Forum")
+ project.update_attribute(:archived, true)
+ end
+
+ Then 'I should see "Shop" project link' do
+ page.should have_link "Shop"
+ end
+
+ Then 'I should not see "Forum" project link' do
+ page.should_not have_link "Forum"
+ end
+
+ Then 'I should see "Forum" project link' do
+ page.should have_link "Forum"
+ end
+end
diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb
index 99ec77a761..81472d1ca3 100644
--- a/features/steps/group/group.rb
+++ b/features/steps/group/group.rb
@@ -1,65 +1,70 @@
class Groups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include SharedGroup
+ include SharedUser
include Select2Helper
- Then 'I should see projects list' do
- current_user.authorized_projects.each do |project|
+ Then 'I should see group "Owned" projects list' do
+ Group.find_by(name: "Owned").projects.each do |project|
page.should have_link project.name
end
end
- And 'I have group with projects' do
- @group = create(:group)
- @group.add_owner(current_user)
- @project = create(:project, namespace: @group)
- @event = create(:closed_issue_event, project: @project)
-
- @project.team << [current_user, :master]
- end
-
And 'I should see projects activity feed' do
page.should have_content 'closed issue'
end
- Then 'I should see issues from this group assigned to me' do
+ Then 'I should see issues from group "Owned" assigned to me' do
assigned_to_me(:issues).each do |issue|
page.should have_content issue.title
end
end
- Then 'I should see merge requests from this group assigned to me' do
+ Then 'I should see merge requests from group "Owned" assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
page.should have_content issue.title[0..80]
end
end
- Given 'I have new user "John"' do
- create(:user, name: "John")
- end
-
- And 'I select user "John" from list with role "Reporter"' do
- user = User.find_by_name("John")
- within ".new_users_group" do
+ And 'I select user "Mary Jane" from list with role "Reporter"' do
+ user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
+ click_link 'Add members'
+ within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "group_access"
end
click_button "Add users into group"
end
- Then 'I should see user "John" in team list' do
+ Then 'I should see user "John Doe" in team list' do
projects_with_access = find(".ui-box .well-list")
- projects_with_access.should have_content("John")
+ projects_with_access.should have_content("John Doe")
end
- Given 'project from group has issues assigned to me' do
+ Then 'I should not see user "John Doe" in team list' do
+ projects_with_access = find(".ui-box .well-list")
+ projects_with_access.should_not have_content("John Doe")
+ end
+
+ Then 'I should see user "Mary Jane" in team list' do
+ projects_with_access = find(".ui-box .well-list")
+ projects_with_access.should have_content("Mary Jane")
+ end
+
+ Then 'I should not see user "Mary Jane" in team list' do
+ projects_with_access = find(".ui-box .well-list")
+ projects_with_access.should_not have_content("Mary Jane")
+ end
+
+ Given 'project from group "Owned" has issues assigned to me' do
create :issue,
project: project,
assignee: current_user,
author: current_user
end
- Given 'project from group has merge requests assigned to me' do
+ Given 'project from group "Owned" has merge requests assigned to me' do
create :merge_request,
source_project: project,
target_project: project,
@@ -71,44 +76,94 @@ class Groups < Spinach::FeatureSteps
click_link "New group"
end
- And 'submit form with new group info' do
+ And 'submit form with new group "Samurai" info' do
fill_in 'group_name', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
- Then 'I should see newly created group' do
+ Then 'I should be redirected to group "Samurai" page' do
+ current_path.should == group_path(Group.last)
+ end
+
+ Then 'I should see newly created group "Samurai"' do
page.should have_content "Samurai"
page.should have_content "Tokugawa Shogunate"
page.should have_content "You will only see events from projects in this group"
end
- Then 'I should be redirected to group page' do
- current_path.should == group_path(Group.last)
- end
-
- And 'I change group name' do
+ And 'I change group "Owned" name to "new-name"' do
fill_in 'group_name', with: 'new-name'
click_button "Save group"
end
- Then 'I should see new group name' do
+ Then 'I should see new group "Owned" name' do
within ".navbar-gitlab" do
page.should have_content "group: new-name"
end
end
+ step 'I change group "Owned" avatar' do
+ attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ click_button "Save group"
+ Group.find_by(name: "Owned").reload
+ end
+
+ step 'I should see new group "Owned" avatar' do
+ Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader
+ Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
+ end
+
+ step 'I should see the "Remove avatar" button' do
+ page.should have_link("Remove avatar")
+ end
+
+ step 'I have group "Owned" avatar' do
+ attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ click_button "Save group"
+ Group.find_by(name: "Owned").reload
+ end
+
+ step 'I remove group "Owned" avatar' do
+ click_link "Remove avatar"
+ Group.find_by(name: "Owned").reload
+ end
+
+ step 'I should not see group "Owned" avatar' do
+ Group.find_by(name: "Owned").avatar?.should be_false
+ end
+
+ step 'I should not see the "Remove avatar" button' do
+ page.should_not have_link("Remove avatar")
+ end
+
+ step 'I click on the "Remove User From Group" button for "John Doe"' do
+ find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I click on the "Remove User From Group" button for "Mary Jane"' do
+ find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Remove User From Group" button for "John Doe"' do
+ find(:css, 'li', text: "John Doe").should_not have_selector(:css, 'a.btn-remove')
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
+ find(:css, 'li', text: "Mary Jane").should_not have_selector(:css, 'a.btn-remove')
+ # poltergeist always confirms popups.
+ end
+
protected
- def current_group
- @group ||= Group.first
- end
-
- def project
- current_group.projects.first
- end
-
def assigned_to_me key
project.send(key).where(assignee_id: current_user.id)
end
+
+ def project
+ Group.find_by(name: "Owned").projects.first
+ end
end
diff --git a/features/steps/help.rb b/features/steps/help.rb
new file mode 100644
index 0000000000..aa147fd65c
--- /dev/null
+++ b/features/steps/help.rb
@@ -0,0 +1,21 @@
+class Spinach::Features::Help < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedMarkdown
+
+ step 'I visit the help page' do
+ visit help_path
+ end
+
+ step 'I visit the "Rake Tasks" help page' do
+ visit help_raketasks_path
+ end
+
+ step 'I should see "Rake Tasks" page markdown rendered' do
+ page.should have_content "GitLab provides some specific rake tasks to enable special features or perform maintenance tasks"
+ end
+
+ step 'Header "Rebuild project satellites" should have correct ids and links' do
+ header_should_have_correct_id_and_link(3, 'Rebuild project satellites', 'rebuild-project-satellites')
+ end
+end
diff --git a/features/steps/profile/profile_active_tab.rb b/features/steps/profile/active_tab.rb
similarity index 100%
rename from features/steps/profile/profile_active_tab.rb
rename to features/steps/profile/active_tab.rb
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
new file mode 100644
index 0000000000..99588c8599
--- /dev/null
+++ b/features/steps/profile/emails.rb
@@ -0,0 +1,48 @@
+class ProfileEmails < Spinach::FeatureSteps
+ include SharedAuthentication
+
+ Then 'I visit profile emails page' do
+ visit profile_emails_path
+ end
+
+ Then 'I should see my emails' do
+ page.should have_content(@user.email)
+ @user.emails.each do |email|
+ page.should have_content(email.email)
+ end
+ end
+
+ And 'I submit new email "my@email.com"' do
+ fill_in "email_email", with: "my@email.com"
+ click_button "Add"
+ end
+
+ Then 'I should see new email "my@email.com"' do
+ email = @user.emails.find_by(email: "my@email.com")
+ email.should_not be_nil
+ page.should have_content("my@email.com")
+ end
+
+ Then 'I should not see email "my@email.com"' do
+ email = @user.emails.find_by(email: "my@email.com")
+ email.should be_nil
+ page.should_not have_content("my@email.com")
+ end
+
+ Then 'I click link "Remove" for "my@email.com"' do
+ # there should only be one remove button at this time
+ click_link "Remove"
+ # force these to reload as they have been cached
+ @user.emails.reload
+ end
+
+ And 'I submit duplicate email @user.email' do
+ fill_in "email_email", with: @user.email
+ click_button "Add"
+ end
+
+ Then 'I should not have @user.email added' do
+ email = @user.emails.find_by(email: @user.email)
+ email.should be_nil
+ end
+end
diff --git a/features/steps/profile/group.rb b/features/steps/profile/group.rb
new file mode 100644
index 0000000000..03144104c7
--- /dev/null
+++ b/features/steps/profile/group.rb
@@ -0,0 +1,44 @@
+class ProfileGroup < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedGroup
+ include SharedPaths
+ include SharedUser
+
+ # Leave
+
+ step 'I click on the "Leave" button for group "Owned"' do
+ find(:css, 'li', text: "Owner").find(:css, 'i.icon-signout').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I click on the "Leave" button for group "Guest"' do
+ find(:css, 'li', text: "Guest").find(:css, 'i.icon-signout').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Leave" button for group "Owned"' do
+ find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.icon-signout')
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Leave" button for groupr "Guest"' do
+ find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.icon-signout')
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should see group "Owned" in group list' do
+ page.should have_content("Owned")
+ end
+
+ step 'I should not see group "Owned" in group list' do
+ page.should_not have_content("Owned")
+ end
+
+ step 'I should see group "Guest" in group list' do
+ page.should have_content("Guest")
+ end
+
+ step 'I should not see group "Guest" in group list' do
+ page.should_not have_content("Guest")
+ end
+end
diff --git a/features/steps/profile/profile_notifications.rb b/features/steps/profile/notifications.rb
similarity index 87%
rename from features/steps/profile/profile_notifications.rb
rename to features/steps/profile/notifications.rb
index 7a41687dfd..e884df3098 100644
--- a/features/steps/profile/profile_notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -8,6 +8,5 @@ class ProfileNotifications < Spinach::FeatureSteps
step 'I should see global notifications settings' do
page.should have_content "Notifications settings"
- page.should have_content "Global setting"
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 753e2c19bc..879bcf41b4 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -6,18 +6,20 @@ class Profile < Spinach::FeatureSteps
page.should have_content "Profile settings"
end
- step 'I change my contact info' do
+ step 'I change my profile info' do
fill_in "user_skype", with: "testskype"
fill_in "user_linkedin", with: "testlinkedin"
fill_in "user_twitter", with: "testtwitter"
+ fill_in "user_website_url", with: "testurl"
click_button "Save changes"
@user.reload
end
- step 'I should see new contact info' do
+ step 'I should see new profile info' do
@user.skype.should == 'testskype'
@user.linkedin.should == 'testlinkedin'
@user.twitter.should == 'testtwitter'
+ @user.website_url.should == 'testurl'
end
step 'I change my avatar' do
@@ -31,26 +33,49 @@ class Profile < Spinach::FeatureSteps
@user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
end
+ step 'I should see the "Remove avatar" button' do
+ page.should have_link("Remove avatar")
+ end
+
+ step 'I have an avatar' do
+ attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ click_button "Save changes"
+ @user.reload
+ end
+
+ step 'I remove my avatar' do
+ click_link "Remove avatar"
+ @user.reload
+ end
+
+ step 'I should see my gravatar' do
+ @user.avatar?.should be_false
+ end
+
+ step 'I should not see the "Remove avatar" button' do
+ page.should_not have_link("Remove avatar")
+ end
+
step 'I try change my password w/o old one' do
within '.update-password' do
- fill_in "user_password", with: "222333"
- fill_in "user_password_confirmation", with: "222333"
+ fill_in "user_password", with: "22233344"
+ fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
end
step 'I change my password' do
within '.update-password' do
- fill_in "user_current_password", with: "123456"
- fill_in "user_password", with: "222333"
- fill_in "user_password_confirmation", with: "222333"
+ fill_in "user_current_password", with: "12345678"
+ fill_in "user_password", with: "22233344"
+ fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
end
step 'I unsuccessfully change my password' do
within '.update-password' do
- fill_in "user_current_password", with: "123456"
+ fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
click_button "Save"
@@ -62,11 +87,7 @@ class Profile < Spinach::FeatureSteps
end
step "I should see a password error message" do
- page.should have_content "Password doesn't match confirmation"
- end
-
- step 'I should be redirected to sign in page' do
- current_path.should == new_user_session_path
+ page.should have_content "Password confirmation doesn't match"
end
step 'I reset my token' do
@@ -152,4 +173,17 @@ class Profile < Spinach::FeatureSteps
page.should have_content current_user.name
end
end
+
+ step 'I have group with projects' do
+ @group = create(:group)
+ @group.add_owner(current_user)
+ @project = create(:project, namespace: @group)
+ @event = create(:closed_issue_event, project: @project)
+
+ @project.team << [current_user, :master]
+ end
+
+ step 'I should see groups I belong to' do
+ page.should have_css('.profile-groups-avatars', visible: true)
+ end
end
diff --git a/features/steps/profile/profile_ssh_keys.rb b/features/steps/profile/ssh_keys.rb
similarity index 97%
rename from features/steps/profile/profile_ssh_keys.rb
rename to features/steps/profile/ssh_keys.rb
index 65bfc505d8..65ca824bb5 100644
--- a/features/steps/profile/profile_ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -18,7 +18,7 @@ class ProfileSshKeys < Spinach::FeatureSteps
end
Then 'I should see new ssh key "Laptop"' do
- key = Key.find_by_title("Laptop")
+ key = Key.find_by(title: "Laptop")
page.should have_content(key.title)
page.should have_content(key.key)
current_path.should == profile_key_path(key)
diff --git a/features/steps/project/project_active_tab.rb b/features/steps/project/active_tab.rb
similarity index 96%
rename from features/steps/project/project_active_tab.rb
rename to features/steps/project/active_tab.rb
index e04a17168b..dcc252f476 100644
--- a/features/steps/project/project_active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -57,7 +57,9 @@ class ProjectActiveTab < Spinach::FeatureSteps
end
Given 'I click the "Edit" tab' do
- click_link('Edit')
+ within '.project-settings-nav' do
+ click_link('Project')
+ end
end
Given 'I click the "Hooks" tab' do
@@ -73,7 +75,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
end
Then 'the active sub nav should be Edit' do
- ensure_active_sub_nav('Edit Project')
+ ensure_active_sub_nav('Project')
end
Then 'the active sub nav should be Hooks' do
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
new file mode 100644
index 0000000000..dfbe762c43
--- /dev/null
+++ b/features/steps/project/archived.rb
@@ -0,0 +1,37 @@
+class ProjectArchived < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ When 'project "Forum" is archived' do
+ project = Project.find_by(name: "Forum")
+ project.update_attribute(:archived, true)
+ end
+
+ When 'project "Shop" is archived' do
+ project = Project.find_by(name: "Shop")
+ project.update_attribute(:archived, true)
+ end
+
+ When 'I visit project "Forum" page' do
+ project = Project.find_by(name: "Forum")
+ visit project_path(project)
+ end
+
+ Then 'I should not see "Archived"' do
+ page.should_not have_content "Archived"
+ end
+
+ Then 'I should see "Archived"' do
+ page.should have_content "Archived"
+ end
+
+ When 'I set project archived' do
+ click_link "Archive"
+ end
+
+ When 'I set project unarchived' do
+ click_link "Unarchive"
+ end
+
+end
\ No newline at end of file
diff --git a/features/steps/project/browse_branches.rb b/features/steps/project/browse_branches.rb
new file mode 100644
index 0000000000..30c8cef80c
--- /dev/null
+++ b/features/steps/project/browse_branches.rb
@@ -0,0 +1,51 @@
+class ProjectBrowseBranches < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I should see "Shop" recent branches list' do
+ page.should have_content "Branches"
+ page.should have_content "master"
+ end
+
+ step 'I click link "All"' do
+ click_link "All"
+ end
+
+ step 'I should see "Shop" all branches list' do
+ page.should have_content "Branches"
+ page.should have_content "master"
+ end
+
+ step 'I click link "Protected"' do
+ click_link "Protected"
+ end
+
+ step 'I should see "Shop" protected branches list' do
+ within ".protected-branches-list" do
+ page.should have_content "stable"
+ page.should_not have_content "master"
+ end
+ end
+
+ step 'project "Shop" has protected branches' do
+ project = Project.find_by(name: "Shop")
+ project.protected_branches.create(name: "stable")
+ end
+
+ step 'I click new branch link' do
+ click_link "New branch"
+ end
+
+ step 'I submit new branch form' do
+ fill_in 'branch_name', with: 'deploy_keys'
+ fill_in 'ref', with: 'master'
+ click_button 'Create branch'
+ end
+
+ step 'I should see new branch created' do
+ within '.all-branches' do
+ page.should have_content 'deploy_keys'
+ end
+ end
+end
diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/browse_commits.rb
similarity index 92%
rename from features/steps/project/project_browse_commits.rb
rename to features/steps/project/browse_commits.rb
index 650bc3a16f..d667b58240 100644
--- a/features/steps/project/project_browse_commits.rb
+++ b/features/steps/project/browse_commits.rb
@@ -88,4 +88,17 @@ class ProjectBrowseCommits < Spinach::FeatureSteps
links[0]['href'].should =~ %r{blob/bc3735004cb45cec5e0e4fa92710897a910a5957}
links[1]['href'].should =~ %r{blob/cc1ba255d6c5ffdce87a357ba7ccc397a4f4026b}
end
+
+ Given 'I click side-by-side diff button' do
+ click_link "Side-by-side Diff"
+ end
+
+ Then 'I see side-by-side diff button' do
+ page.should have_content "Side-by-side Diff"
+ end
+
+ Then 'I see inline diff button' do
+ page.should have_content "Inline Diff"
+ end
+
end
diff --git a/features/steps/project/browse_commits_user_lookup.rb b/features/steps/project/browse_commits_user_lookup.rb
new file mode 100644
index 0000000000..328be37355
--- /dev/null
+++ b/features/steps/project/browse_commits_user_lookup.rb
@@ -0,0 +1,35 @@
+class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ Given 'I have the user that authored the commits' do
+ @user = create(:user, email: 'dmitriy.zaporozhets@gmail.com')
+ create(:email, { user: @user, email: 'dzaporozhets@sphereconsultinginc.com' })
+ end
+
+ Given 'I click on commit link' do
+ visit project_commit_path(@project, ValidCommit::ID)
+ end
+
+ Given 'I click on another commit link' do
+ visit project_commit_path(@project, ValidCommitWithAltEmail::ID)
+ end
+
+ Then 'I see commit info' do
+ page.should have_content ValidCommit::MESSAGE
+ check_author_link(ValidCommit::AUTHOR_EMAIL)
+ end
+
+ Then 'I see other commit info' do
+ page.should have_content ValidCommitWithAltEmail::MESSAGE
+ check_author_link(ValidCommitWithAltEmail::AUTHOR_EMAIL)
+ end
+
+ def check_author_link(email)
+ author_link = find('.commit-author-link')
+ author_link['href'].should == user_path(@user)
+ author_link['data-original-title'].should == email
+ find('.commit-author-name').text.should == @user.name
+ end
+end
diff --git a/features/steps/project/browse_files.rb b/features/steps/project/browse_files.rb
new file mode 100644
index 0000000000..7cdd1101ac
--- /dev/null
+++ b/features/steps/project/browse_files.rb
@@ -0,0 +1,65 @@
+class ProjectBrowseFiles < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I should see files from repository' do
+ page.should have_content "app"
+ page.should have_content "history"
+ page.should have_content "Gemfile"
+ end
+
+ step 'I should see files from repository for "8470d70"' do
+ current_path.should == project_tree_path(@project, "8470d70")
+ page.should have_content "app"
+ page.should have_content "history"
+ page.should have_content "Gemfile"
+ end
+
+ step 'I click on "Gemfile.lock" file in repo' do
+ click_link "Gemfile.lock"
+ end
+
+ step 'I should see it content' do
+ page.should have_content "DEPENDENCIES"
+ end
+
+ step 'I click link "raw"' do
+ click_link "raw"
+ end
+
+ step 'I should see raw file content' do
+ page.source.should == ValidCommit::BLOB_FILE
+ end
+
+ step 'I click button "edit"' do
+ click_link 'edit'
+ end
+
+ step 'I can edit code' do
+ page.execute_script('editor.setValue("GitlabFileEditor")')
+ page.evaluate_script('editor.getValue()').should == "GitlabFileEditor"
+ end
+
+ step 'I edit code' do
+ page.execute_script('editor.setValue("GitlabFileEditor")')
+ end
+
+ step 'I click link "Diff"' do
+ click_link 'Diff'
+ end
+
+ step 'I see diff' do
+ page.should have_css '.line_holder.new'
+ end
+
+ step 'I click on "new file" link in repo' do
+ click_link 'new-file-link'
+ end
+
+ step 'I can see new file page' do
+ page.should have_content "New file"
+ page.should have_content "File name"
+ page.should have_content "Commit message"
+ end
+end
diff --git a/features/steps/project/project_browse_git_repo.rb b/features/steps/project/browse_git_repo.rb
similarity index 100%
rename from features/steps/project/project_browse_git_repo.rb
rename to features/steps/project/browse_git_repo.rb
diff --git a/features/steps/project/project_browse_tags.rb b/features/steps/project/browse_tags.rb
similarity index 100%
rename from features/steps/project/project_browse_tags.rb
rename to features/steps/project/browse_tags.rb
diff --git a/features/steps/project/create_project.rb b/features/steps/project/create.rb
similarity index 100%
rename from features/steps/project/create_project.rb
rename to features/steps/project/create.rb
index b59345e707..b42e5bd362 100644
--- a/features/steps/project/create_project.rb
+++ b/features/steps/project/create.rb
@@ -8,8 +8,8 @@ class CreateProject < Spinach::FeatureSteps
end
Then 'I should see project page' do
- current_path.should == project_path(Project.last)
page.should have_content "Empty"
+ current_path.should == project_path(Project.last)
end
And 'I should see empty project instuctions' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 7f7492bfd6..914da31322 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -34,7 +34,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'other project has deploy key' do
- @second_project = create :project, namespace: current_user.namespace
+ @second_project = create :project, namespace: create(:group)
@second_project.team << [current_user, :master]
create(:deploy_keys_project, project: @second_project)
end
diff --git a/features/steps/project/filter_labels.rb b/features/steps/project/filter_labels.rb
new file mode 100644
index 0000000000..5926d69d6c
--- /dev/null
+++ b/features/steps/project/filter_labels.rb
@@ -0,0 +1,70 @@
+class ProjectFilterLabels < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ Then 'I should see "bug" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "bug"
+ end
+ end
+
+ And 'I should see "feature" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "feature"
+ end
+ end
+
+ And 'I should see "enhancement" in labels filter' do
+ within ".labels-filter" do
+ page.should have_content "enhancement"
+ end
+ end
+
+ Then 'I should see "Bugfix1" in issues list' do
+ within ".issues-list" do
+ page.should have_content "Bugfix1"
+ end
+ end
+
+ And 'I should see "Bugfix2" in issues list' do
+ within ".issues-list" do
+ page.should have_content "Bugfix2"
+ end
+ end
+
+ And 'I should not see "Bugfix2" in issues list' do
+ within ".issues-list" do
+ page.should_not have_content "Bugfix2"
+ end
+ end
+
+ And 'I should not see "Feature1" in issues list' do
+ within ".issues-list" do
+ page.should_not have_content "Feature1"
+ end
+ end
+
+ Given 'I click link "bug"' do
+ click_link "bug"
+ end
+
+ Given 'I click link "feature"' do
+ click_link "feature"
+ end
+
+ And 'project "Shop" has issue "Bugfix1" with tags: "bug", "feature"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Bugfix1", project: project, label_list: ['bug', 'feature'])
+ end
+
+ And 'project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Bugfix2", project: project, label_list: ['bug', 'enhancement'])
+ end
+
+ And 'project "Shop" has issue "Feature1" with tags: "feature"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, title: "Feature1", project: project, label_list: 'feature')
+ end
+end
diff --git a/features/steps/project/project_fork.rb b/features/steps/project/fork.rb
similarity index 76%
rename from features/steps/project/project_fork.rb
rename to features/steps/project/fork.rb
index 858c7d11b3..c00d9014b1 100644
--- a/features/steps/project/project_fork.rb
+++ b/features/steps/project/fork.rb
@@ -11,22 +11,22 @@ class ForkProject < Spinach::FeatureSteps
end
step 'I am a member of project "Shop"' do
- @project = Project.find_by_name "Shop"
- @project ||= create(:project_with_code, name: "Shop", group: create(:group))
+ @project = Project.find_by(name: "Shop")
+ @project ||= create(:project, name: "Shop", group: create(:group))
@project.team << [@user, :reporter]
end
step 'I should see the forked project page' do
page.should have_content "Project was successfully forked."
current_path.should include current_user.namespace.path
- @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
+ @forked_project = Project.find_by(namespace_id: current_user.namespace.path)
end
step 'I already have a project named "Shop" in my namespace' do
current_user.namespace ||= create(:namespace)
current_user.namespace.should_not be_nil
current_user.namespace.path.should_not be_nil
- @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
+ @my_project = create(:project, name: "Shop", namespace: current_user.namespace)
end
step 'I should see a "Name has already been taken" warning' do
diff --git a/features/steps/project/project_forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
similarity index 79%
rename from features/steps/project/project_forked_merge_requests.rb
rename to features/steps/project/forked_merge_requests.rb
index f7bf085a42..3c497638d9 100644
--- a/features/steps/project/project_forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -3,19 +3,19 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
- include ChosenHelper
+ include Select2Helper
step 'I am a member of project "Shop"' do
- @project = Project.find_by_name "Shop"
- @project ||= create(:project_with_code, name: "Shop")
+ @project = Project.find_by(name: "Shop")
+ @project ||= create(:project, name: "Shop")
@project.team << [@user, :reporter]
end
step 'I have a project forked off of "Shop" called "Forked Shop"' do
@forking_user = @user
forked_project_link = build(:forked_project_link)
- @forked_project = Project.find_by_name "Forked Shop"
- @forked_project ||= create(:source_project_with_code, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace)
+ @forked_project = Project.find_by(name: "Forked Shop")
+ @forked_project ||= create(:project, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace)
forked_project_link.forked_from_project = @project
forked_project_link.forked_to_project = @forked_project
@@ -42,17 +42,18 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
- chosen @forked_project.id, from: "#merge_request_source_project_id"
- chosen @project.id, from: "#merge_request_target_project_id"
+ select2 @forked_project.id, from: "#merge_request_source_project_id"
+ select2 @project.id, from: "#merge_request_target_project_id"
find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
- chosen "master", from: "#merge_request_source_branch"
- chosen "stable", from: "#merge_request_target_branch"
+ select2 "master", from: "#merge_request_source_branch"
+ select2 "stable", from: "#merge_request_target_branch"
find(:select, "merge_request_source_branch", {}).value.should == 'master'
find(:select, "merge_request_target_branch", {}).value.should == 'stable'
+ click_button "Compare branches"
fill_in "merge_request_title", with: "Merge Request On Forked Project"
end
@@ -114,7 +115,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'project "Forked Shop" has push event' do
- @forked_project = Project.find_by_name("Forked Shop")
+ @forked_project = Project.find_by(name: "Forked Shop")
data = {
before: "0000000000000000000000000000000000000000",
@@ -148,31 +149,27 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps
current_path.should == edit_project_merge_request_path(@project, @merge_request)
page.should have_content "Edit merge request ##{@merge_request.id}"
find("#merge_request_title").value.should == "Merge Request On Forked Project"
- find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
- find("#merge_request_target_project_id").value.should == @project.id.to_s
- find("#merge_request_source_branch").value.should have_content "master"
- verify_commit_link(".mr_source_commit",@forked_project)
- find("#merge_request_target_branch").value.should have_content "stable"
- verify_commit_link(".mr_target_commit",@project)
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
- #If this isn't filled in the rest of the validations won't be triggered
- fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ select "Select branch", from: "merge_request_target_branch"
find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
- find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
+ find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
find(:select, "merge_request_source_branch", {}).value.should == ""
find(:select, "merge_request_target_branch", {}).value.should == ""
+ click_button "Compare branches"
end
step 'I should see validation errors' do
- page.should have_content "Source branch can't be blank"
- page.should have_content "Target branch can't be blank"
- page.should have_content "Branch conflict You can not use same project/branch for source and target"
+ page.should have_content "You must select source and target branch"
+ end
+
+ step 'the target repository should be the original repository' do
+ page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace)
end
def project
- @project ||= Project.find_by_name!("Shop")
+ @project ||= Project.find_by!(name: "Shop")
end
# Verify a link is generated against the correct project
diff --git a/features/steps/project/project_graph.rb b/features/steps/project/graph.rb
similarity index 86%
rename from features/steps/project/project_graph.rb
rename to features/steps/project/graph.rb
index 50942b3cbb..89fe5fdead 100644
--- a/features/steps/project/project_graph.rb
+++ b/features/steps/project/graph.rb
@@ -7,7 +7,7 @@ class ProjectGraph < Spinach::FeatureSteps
end
When 'I visit project "Shop" graph page' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
visit project_graph_path(project, "master")
end
end
diff --git a/features/steps/project/project_hooks.rb b/features/steps/project/hooks.rb
similarity index 88%
rename from features/steps/project/project_hooks.rb
rename to features/steps/project/hooks.rb
index 36555fb8e8..19ff324454 100644
--- a/features/steps/project/project_hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -1,9 +1,12 @@
+require 'webmock'
+
class ProjectHooks < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include RSpec::Matchers
include RSpec::Mocks::ExampleMethods
+ include WebMock::API
Given 'project has hook' do
@hook = create(:project_hook, project: current_project)
@@ -25,8 +28,7 @@ class ProjectHooks < Spinach::FeatureSteps
end
When 'I click test hook button' do
- test_hook_context = double(execute: true)
- TestHookContext.should_receive(:new).and_return(test_hook_context)
+ stub_request(:post, @hook.url).to_return(status: 200)
click_link 'Test Hook'
end
diff --git a/features/steps/project/issue_tracker.rb b/features/steps/project/issue_tracker.rb
new file mode 100644
index 0000000000..c2fd4e15c9
--- /dev/null
+++ b/features/steps/project/issue_tracker.rb
@@ -0,0 +1,31 @@
+class ProjectIssueTracker < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'project "Shop" has issues enabled' do
+ @project = Project.find_by(name: "Shop")
+ @project ||= create(:project, name: "Shop", namespace: @user.namespace)
+ @project.issues_enabled = true
+ end
+
+ step 'change the issue tracker to "GitLab"' do
+ select 'GitLab', from: 'project_issues_tracker'
+ end
+
+ step 'I the project should have "GitLab" as issue tracker' do
+ find_field('project_issues_tracker').value.should == 'gitlab'
+ end
+
+ step 'change the issue tracker to "Redmine"' do
+ select 'Redmine', from: 'project_issues_tracker'
+ end
+
+ step 'I the project should have "Redmine" as issue tracker' do
+ find_field('project_issues_tracker').value.should == 'redmine'
+ end
+
+ And 'I save project' do
+ click_button 'Save changes'
+ end
+end
diff --git a/features/steps/project/project_issues.rb b/features/steps/project/issues.rb
similarity index 75%
rename from features/steps/project/project_issues.rb
rename to features/steps/project/issues.rb
index 801fff78a5..d1f3ba25a2 100644
--- a/features/steps/project/project_issues.rb
+++ b/features/steps/project/issues.rb
@@ -3,6 +3,7 @@ class ProjectIssues < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
+ include SharedMarkdown
Given 'I should see "Release 0.4" in issues' do
page.should have_content "Release 0.4"
@@ -54,7 +55,7 @@ class ProjectIssues < Spinach::FeatureSteps
end
Then 'I should see issue "500 error on profile"' do
- issue = Issue.find_by_title("500 error on profile")
+ issue = Issue.find_by(title: "500 error on profile")
page.should have_content issue.title
page.should have_content issue.author_name
page.should have_content issue.project.name
@@ -81,14 +82,14 @@ class ProjectIssues < Spinach::FeatureSteps
end
Given 'project "Shop" has milestone "v2.2"' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
milestone = create(:milestone, title: "v2.2", project: project)
3.times { create(:issue, project: project, milestone: milestone) }
end
And 'project "Shop" has milestone "v3.0"' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
milestone = create(:milestone, title: "v3.0", project: project)
3.times { create(:issue, project: project, milestone: milestone) }
@@ -104,28 +105,30 @@ class ProjectIssues < Spinach::FeatureSteps
end
When 'I select first assignee from "Shop" project' do
- project = Project.find_by_name "Shop"
+ project = Project.find_by(name: "Shop")
first_assignee = project.users.first
select first_assignee.name, from: "assignee_id"
end
Then 'I should see first assignee from "Shop" as selected assignee' do
issues_assignee_selector = "#issue_assignee_id_chzn > a"
- project = Project.find_by_name "Shop"
+ project = Project.find_by(name: "Shop")
assignee_name = project.users.first.name
page.find(issues_assignee_selector).should have_content(assignee_name)
end
And 'project "Shop" have "Release 0.4" open issue' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
create(:issue,
title: "Release 0.4",
project: project,
- author: project.users.first)
+ author: project.users.first,
+ description: "# Description header"
+ )
end
And 'project "Shop" have "Tweet control" open issue' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
create(:issue,
title: "Tweet control",
project: project,
@@ -133,10 +136,31 @@ class ProjectIssues < Spinach::FeatureSteps
end
And 'project "Shop" have "Release 0.3" closed issue' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
create(:closed_issue,
title: "Release 0.3",
project: project,
author: project.users.first)
end
+
+ Given 'empty project "Empty Project"' do
+ create :empty_project, name: 'Empty Project', namespace: @user.namespace
+ end
+
+ When 'I visit empty project page' do
+ project = Project.find_by(name: 'Empty Project')
+ visit project_path(project)
+ end
+
+ And 'I see empty project details with ssh clone info' do
+ project = Project.find_by(name: 'Empty Project')
+ page.all(:css, '.git-empty .clone').each do |element|
+ element.text.should include(project.url_to_repo)
+ end
+ end
+
+ When "I visit empty project's issues page" do
+ project = Project.find_by(name: 'Empty Project')
+ visit project_issues_path(project)
+ end
end
diff --git a/features/steps/project/project_labels.rb b/features/steps/project/labels.rb
similarity index 92%
rename from features/steps/project/project_labels.rb
rename to features/steps/project/labels.rb
index 915190f3da..0907cdb526 100644
--- a/features/steps/project/project_labels.rb
+++ b/features/steps/project/labels.rb
@@ -16,7 +16,7 @@ class ProjectLabels < Spinach::FeatureSteps
end
And 'project "Shop" have issues tags: "bug", "feature"' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
['bug', 'feature'].each do |label|
create(:issue, project: project, label_list: label)
end
diff --git a/features/steps/project/project_markdown_render.rb b/features/steps/project/markdown_render.rb
similarity index 52%
rename from features/steps/project/project_markdown_render.rb
rename to features/steps/project/markdown_render.rb
index 95d73c9362..c94de28306 100644
--- a/features/steps/project/project_markdown_render.rb
+++ b/features/steps/project/markdown_render.rb
@@ -1,10 +1,14 @@
+# If you need to modify the existing seed repository for your tests,
+# it is recommended that you make the changes on the `markdown` branch of the seed project repository,
+# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info.
class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
+ include SharedMarkdown
And 'I own project "Delta"' do
- @project = Project.find_by_name "Delta"
- @project ||= create(:project_with_code, name: "Delta", namespace: @user.namespace)
+ @project = Project.find_by(name: "Delta")
+ @project ||= create(:project, name: "Delta", namespace: @user.namespace)
@project.team << [@user, :master]
end
@@ -21,6 +25,8 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_link "GitLab API website"
page.should have_link "Rake tasks"
page.should have_link "backup and restore procedure"
+ page.should have_link "GitLab API doc directory"
+ page.should have_link "Maintenance"
end
And 'I click on Gitlab API in README' do
@@ -42,6 +48,37 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_content "maintenance.md"
end
+ And 'I click on GitLab API doc directory in README' do
+ click_link "GitLab API doc directory"
+ end
+
+ Then 'I should see correct doc/api directory rendered' do
+ current_path.should == project_tree_path(@project, "master/doc/api")
+ page.should have_content "README.md"
+ page.should have_content "users.md"
+ end
+
+ And 'I click on Maintenance in README' do
+ click_link "Maintenance"
+ end
+
+ Then 'I should see correct maintenance file rendered' do
+ current_path.should == project_blob_path(@project, "master/doc/raketasks/maintenance.md")
+ page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
+ end
+
+ And 'I click on link "empty" in the README' do
+ within('.readme-holder') do
+ click_link "empty"
+ end
+ end
+
+ And 'I click on link "id" in the README' do
+ within('.readme-holder') do
+ click_link "#id"
+ end
+ end
+
And 'I navigate to the doc/api/README' do
click_link "doc"
click_link "api"
@@ -68,10 +105,24 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
click_link "Rake tasks"
end
+ # Markdown branch
+
When 'I visit markdown branch' do
visit project_tree_path(@project, "markdown")
end
+ When 'I visit markdown branch "README.md" blob' do
+ visit project_blob_path(@project, "markdown/README.md")
+ end
+
+ When 'I visit markdown branch "d" tree' do
+ visit project_tree_path(@project, "markdown/d")
+ end
+
+ When 'I visit markdown branch "d/README.md" blob' do
+ visit project_blob_path(@project, "markdown/d/README.md")
+ end
+
Then 'I should see files from repository in markdown branch' do
current_path.should == project_tree_path(@project, "markdown")
page.should have_content "Gemfile"
@@ -102,13 +153,57 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_content "Get a list of users."
end
+ # Expected link contents
+
+ Then 'The link with text "empty" should have url "tree/markdown"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
+ end
+
+ Then 'The link with text "empty" should have url "blob/markdown/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
+ end
+
+ Then 'The link with text "empty" should have url "tree/markdown/d"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
+ end
+
+ Then 'The link with text "empty" should have url "blob/markdown/d/README.md"' do
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
+ end
+
+ Then 'The link with text "ID" should have url "tree/markdownID"' do
+ find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ end
+
+ Then 'The link with text "/ID" should have url "tree/markdownID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ end
+
+ Then 'The link with text "README.mdID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"' do
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ Then 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
+ find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ end
+
+ # Wiki
+
Given 'I go to wiki page' do
click_link "Wiki"
current_path.should == project_wiki_path(@project, "home")
end
And 'I add various links to the wiki page' do
- fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](doc/api/README.md)\n[Rake tasks](doc/raketasks)\n"
+ fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n"
fill_in "wiki[message]", with: "Adding links to wiki"
click_button "Create page"
end
@@ -118,13 +213,23 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
page.should have_content "test GitLab API doc Rake tasks"
end
+ step 'I add a header to the wiki page' do
+ fill_in "wiki[content]", with: "# Wiki header\n"
+ fill_in "wiki[message]", with: "Add header to wiki"
+ click_button "Create page"
+ end
+
+ step 'Wiki header should have correct id and link' do
+ header_should_have_correct_id_and_link(1, 'Wiki header', 'wiki-header')
+ end
+
And 'I click on test link' do
click_link "test"
end
Then 'I see new wiki page named test' do
current_path.should == project_wiki_path(@project, "test")
- page.should have_content "Editing page"
+ page.should have_content "Editing"
end
When 'I go back to wiki page home' do
@@ -137,8 +242,8 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
end
Then 'I see Gitlab API document' do
- current_path.should == project_blob_path(@project, "master/doc/api/README.md")
- page.should have_content "Status codes"
+ current_path.should == project_wiki_path(@project, "api")
+ page.should have_content "Editing"
end
And 'I click on Rake tasks link' do
@@ -146,21 +251,8 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
end
Then 'I see Rake tasks directory' do
- current_path.should == project_tree_path(@project, "master/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
- end
-
- Given 'I visit to the help page' do
- visit help_path
- end
-
- And 'I select a page with markdown' do
- click_link "Rake Tasks"
- end
-
- Then 'I should see a help page with markdown' do
- page.should have_content "GitLab provides some specific rake tasks to enable special features or perform maintenance tasks"
+ current_path.should == project_wiki_path(@project, "raketasks")
+ page.should have_content "Editing"
end
Given 'I go directory which contains README file' do
@@ -176,4 +268,12 @@ class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps
current_path.should == project_blob_path(@project, "master/doc/api/users.md")
page.should have_content "List users"
end
+
+ step 'Header "Application details" should have correct id and link' do
+ header_should_have_correct_id_and_link(2, 'Application details', 'application-details')
+ end
+
+ step 'Header "GitLab API" should have correct id and link' do
+ header_should_have_correct_id_and_link(1, 'GitLab API', 'gitlab-api')
+ end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
new file mode 100644
index 0000000000..e0aec699a5
--- /dev/null
+++ b/features/steps/project/merge_requests.rb
@@ -0,0 +1,276 @@
+class ProjectMergeRequests < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedNote
+ include SharedPaths
+ include SharedMarkdown
+
+ step 'I click link "New Merge Request"' do
+ click_link "New Merge Request"
+ end
+
+ step 'I click link "Bug NS-04"' do
+ click_link "Bug NS-04"
+ end
+
+ step 'I click link "All"' do
+ click_link "All"
+ end
+
+ step 'I click link "Closed"' do
+ click_link "Closed"
+ end
+
+ step 'I should see merge request "Wiki Feature"' do
+ within '.merge-request' do
+ page.should have_content "Wiki Feature"
+ end
+ end
+
+ step 'I should see closed merge request "Bug NS-04"' do
+ merge_request = MergeRequest.find_by!(title: "Bug NS-04")
+ merge_request.closed?.should be_true
+ page.should have_content "Closed by"
+ end
+
+ step 'I should see merge request "Bug NS-04"' do
+ page.should have_content "Bug NS-04"
+ end
+
+ step 'I should see "Bug NS-04" in merge requests' do
+ page.should have_content "Bug NS-04"
+ end
+
+ step 'I should see "Feature NS-03" in merge requests' do
+ page.should have_content "Feature NS-03"
+ end
+
+ step 'I should not see "Feature NS-03" in merge requests' do
+ page.should_not have_content "Feature NS-03"
+ end
+
+
+ step 'I should not see "Bug NS-04" in merge requests' do
+ page.should_not have_content "Bug NS-04"
+ end
+
+ step 'I click link "Close"' do
+ within '.page-title' do
+ click_link "Close"
+ end
+ end
+
+ step 'I submit new merge request "Wiki Feature"' do
+ select "master", from: "merge_request_source_branch"
+ select "notes_refactoring", from: "merge_request_target_branch"
+ click_button "Compare branches"
+ fill_in "merge_request_title", with: "Wiki Feature"
+ click_button "Submit merge request"
+ end
+
+ step 'project "Shop" have "Bug NS-04" open merge request' do
+ create(:merge_request,
+ title: "Bug NS-04",
+ source_project: project,
+ target_project: project,
+ source_branch: 'stable',
+ target_branch: 'master',
+ author: project.users.first,
+ description: "# Description header"
+ )
+ end
+
+ step 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
+ create(:merge_request_with_diffs,
+ title: "Bug NS-05",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
+ step 'project "Shop" have "Feature NS-03" closed merge request' do
+ create(:closed_merge_request,
+ title: "Feature NS-03",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
+ step 'I switch to the diff tab' do
+ visit diffs_project_merge_request_path(project, merge_request)
+ end
+
+ step 'I switch to the merge request\'s comments tab' do
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ step 'I click on the first commit in the merge request' do
+ within '.first-commits' do
+ click_link merge_request.commits.first.short_id(8)
+ end
+ end
+
+ step 'I leave a comment on the diff page' do
+ init_diff_note
+ leave_comment "One comment to rule them all"
+ end
+
+ step 'I leave a comment on the diff page in commit' do
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
+ leave_comment "One comment to rule them all"
+ end
+
+ step 'I leave a comment like "Line is wrong" on line 185 of the first file' do
+ init_diff_note
+ leave_comment "Line is wrong"
+ end
+
+ step 'I leave a comment like "Line is wrong" on line 185 of the first file in commit' do
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
+ leave_comment "Line is wrong"
+ end
+
+ step 'I should see a discussion has started on line 185' do
+ page.should have_content "#{current_user.name} started a discussion on this merge request diff"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
+ page.should have_content "Line is wrong"
+ end
+
+ step 'I should see a discussion has started on commit b1e6a9dbf1:L185' do
+ page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
+ page.should have_content "Line is wrong"
+ end
+
+ step 'I should see a discussion has started on commit b1e6a9dbf1' do
+ page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "One comment to rule them all"
+ page.should have_content "app/assets/stylesheets/tree.scss:L185"
+ end
+
+ step 'merge request is mergeable' do
+ page.should have_content 'You can accept this request automatically'
+ end
+
+ step 'I modify merge commit message' do
+ find('.modify-merge-commit-link').click
+ fill_in 'merge_commit_message', with: "wow such merge"
+ end
+
+ step 'merge request "Bug NS-05" is mergeable' do
+ merge_request.mark_as_mergeable
+ end
+
+ step 'I accept this merge request' do
+ click_button "Accept Merge Request"
+ end
+
+ step 'I should see merged request' do
+ within '.issue-box' do
+ page.should have_content "Merged"
+ end
+ end
+
+ step 'I click link "Reopen"' do
+ within '.page-title' do
+ click_link "Reopen"
+ end
+ end
+
+ step 'I should see reopened merge request "Bug NS-04"' do
+ within '.state-label' do
+ page.should have_content "Open"
+ end
+ end
+
+ step 'I click link "Hide inline discussion" of the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ click_link "Diff comments"
+ end
+ end
+
+ step 'I click link "Show inline discussion" of the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ click_link "Diff comments"
+ end
+ end
+
+ step 'I should not see a comment like "Line is wrong" in the second file' do
+ within '.files [id^=diff]:nth-child(2)' do
+ page.should_not have_visible_content "Line is wrong"
+ end
+ end
+
+ step 'I should see a comment like "Line is wrong" in the second file' do
+ within '.files [id^=diff]:nth-child(2) .note-text' do
+ page.should have_visible_content "Line is wrong"
+ end
+ end
+
+ step 'I leave a comment like "Line is correct" on line 12 of the first file' do
+ init_diff_note_first_file
+
+ within(".js-discussion-note-form") do
+ fill_in "note_note", with: "Line is correct"
+ click_button "Add Comment"
+ end
+
+ within ".files [id^=diff]:nth-child(1) .note-text" do
+ page.should have_content "Line is correct"
+ end
+ end
+
+ step 'I leave a comment like "Line is wrong" on line 39 of the second file' do
+ init_diff_note_second_file
+
+ within(".js-discussion-note-form") do
+ fill_in "note_note", with: "Line is wrong"
+ click_button "Add Comment"
+ end
+
+ within ".files [id^=diff]:nth-child(2) .note-text" do
+ page.should have_content "Line is wrong"
+ end
+ end
+
+ step 'I should still see a comment like "Line is correct" in the first file' do
+ within '.files [id^=diff]:nth-child(1) .note-text' do
+ page.should have_visible_content "Line is correct"
+ end
+ end
+
+ def project
+ @project ||= Project.find_by!(name: "Shop")
+ end
+
+ def merge_request
+ @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
+ end
+
+ def init_diff_note
+ find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_172_185"]').click
+ end
+
+ def leave_comment(message)
+ within(".js-discussion-note-form") do
+ fill_in "note_note", with: message
+ click_button "Add Comment"
+ end
+
+ within ".note-text" do
+ page.should have_content message
+ end
+ end
+
+ def init_diff_note_first_file
+ find('a[data-line-code="a5cc2925ca8258af241be7e5b0381edf30266302_12_12"]').click
+ end
+
+ def init_diff_note_second_file
+ find('a[data-line-code="8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_28_39"]').click
+ end
+
+ def have_visible_content (text)
+ have_css("*", text: text, visible: true)
+ end
+end
diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/milestones.rb
similarity index 72%
rename from features/steps/project/project_milestones.rb
rename to features/steps/project/milestones.rb
index c4d0d176f3..9ce18fbaab 100644
--- a/features/steps/project/project_milestones.rb
+++ b/features/steps/project/milestones.rb
@@ -2,9 +2,10 @@ class ProjectMilestones < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
+ include SharedMarkdown
Then 'I should see milestone "v2.2"' do
- milestone = @project.milestones.find_by_title("v2.2")
+ milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues")
@@ -24,22 +25,25 @@ class ProjectMilestones < Spinach::FeatureSteps
end
Then 'I should see milestone "v2.3"' do
- milestone = @project.milestones.find_by_title("v2.3")
+ milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues")
end
And 'project "Shop" has milestone "v2.2"' do
- project = Project.find_by_name("Shop")
- milestone = create(:milestone, title: "v2.2", project: project)
-
+ project = Project.find_by(name: "Shop")
+ milestone = create(:milestone,
+ title: "v2.2",
+ project: project,
+ description: "# Description header"
+ )
3.times { create(:issue, project: project, milestone: milestone) }
end
Given 'the milestone has open and closed issues' do
- project = Project.find_by_name("Shop")
- milestone = project.milestones.find_by_title('v2.2')
+ project = Project.find_by(name: "Shop")
+ milestone = project.milestones.find_by(title: 'v2.2')
# 3 Open issues created above; create one closed issue
create(:closed_issue, project: project, milestone: milestone)
diff --git a/features/steps/project/multiselect_blob.rb b/features/steps/project/multiselect_blob.rb
new file mode 100644
index 0000000000..3d330e837c
--- /dev/null
+++ b/features/steps/project/multiselect_blob.rb
@@ -0,0 +1,58 @@
+class ProjectMultiselectBlob < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ class << self
+ def click_line_steps(*line_numbers)
+ line_numbers.each do |line_number|
+ step "I click line #{line_number} in file" do
+ find("#L#{line_number}").click
+ end
+
+ step "I shift-click line #{line_number} in file" do
+ script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));"
+ page.evaluate_script(script)
+ end
+ end
+ end
+
+ def check_state_steps(*ranges)
+ ranges.each do |range|
+ fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}"
+ pluralization = range.kind_of?(Array) ? "s" : ""
+
+ step "I should see \"#{fragment}\" as URI fragment" do
+ URI.parse(current_url).fragment.should == fragment
+ end
+
+ step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do
+ ids = Array(range).map { |n| "LC#{n}" }
+ extra = false
+
+ highlighted = all("#tree-content-holder .highlight .line.hll")
+ highlighted.each do |element|
+ extra ||= ids.delete(element[:id]).nil?
+ end
+
+ extra.should be_false and ids.should be_empty
+ end
+ end
+ end
+ end
+
+ click_line_steps *Array(1..5)
+ check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5)
+
+ step 'I go back in history' do
+ page.evaluate_script("window.history.back()")
+ end
+
+ step 'I go forward in history' do
+ page.evaluate_script("window.history.forward()")
+ end
+
+ step 'I click on "Gemfile.lock" file in repo' do
+ click_link "Gemfile.lock"
+ end
+end
\ No newline at end of file
diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/network_graph.rb
similarity index 77%
rename from features/steps/project/project_network_graph.rb
rename to features/steps/project/network_graph.rb
index 127adecf7e..1c5cfcc6c6 100644
--- a/features/steps/project/project_network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -1,5 +1,6 @@
class ProjectNetworkGraph < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedPaths
include SharedProject
Then 'page should have network graph' do
@@ -10,16 +11,16 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
visit project_network_path(project, "master")
end
And 'page should select "master" in select box' do
- page.should have_selector '.chosen-single span', text: "master"
+ page.should have_selector '.select2-chosen', text: "master"
end
And 'page should select "v2.1.0" in select box' do
- page.should have_selector '.chosen-single span', text: "v2.1.0"
+ page.should have_selector '.select2-chosen', text: "v2.1.0"
end
And 'page should have "master" on graph' do
@@ -43,24 +44,24 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
sleep 2
end
- Then 'page should have content not cotaining "v2.1.0"' do
+ Then 'page should have content not containing "v2.1.0"' do
within '.network-graph' do
page.should have_content 'cleaning'
end
end
- Then 'page should not have content not cotaining "v2.1.0"' do
+ Then 'page should not have content not containing "v2.1.0"' do
within '.network-graph' do
page.should_not have_content 'cleaning'
end
end
And 'page should select "stable" in select box' do
- page.should have_selector '.chosen-single span', text: "stable"
+ page.should have_selector '.select2-chosen', text: "stable"
end
And 'page should select "v2.1.0" in select box' do
- page.should have_selector '.chosen-single span', text: "v2.1.0"
+ page.should have_selector '.select2-chosen', text: "v2.1.0"
end
And 'page should have "stable" on graph' do
@@ -70,7 +71,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
end
When 'I looking for a commit by SHA of "v2.1.0"' do
- within ".content .search" do
+ within ".network-form" do
fill_in 'extended_sha1', with: '98d6492'
find('button').click
end
@@ -84,7 +85,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps
end
When 'I look for a commit by ";"' do
- within ".content .search" do
+ within ".network-form" do
fill_in 'extended_sha1', with: ';'
find('button').click
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index a96b086fae..92728d474b 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -3,16 +3,25 @@ class ProjectFeature < Spinach::FeatureSteps
include SharedProject
include SharedPaths
- And 'change project settings' do
+ step 'change project settings' do
fill_in 'project_name', with: 'NewName'
uncheck 'project_issues_enabled'
end
- And 'I save project' do
+ step 'I save project' do
click_button 'Save changes'
end
- Then 'I should see project with new settings' do
+ step 'I should see project with new settings' do
find_field('project_name').value.should == 'NewName'
end
+
+ step 'change project path settings' do
+ fill_in "project_path", with: "new-path"
+ click_button "Rename"
+ end
+
+ step 'I should see project with new path settings' do
+ project.path.should == "new-path"
+ end
end
diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb
deleted file mode 100644
index e77825411f..0000000000
--- a/features/steps/project/project_browse_branches.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-class ProjectBrowseBranches < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- Then 'I should see "Shop" recent branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
- end
-
- Given 'I click link "All"' do
- click_link "All"
- end
-
- Then 'I should see "Shop" all branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
- end
-
- Given 'I click link "Protected"' do
- click_link "Protected"
- end
-
- Then 'I should see "Shop" protected branches list' do
- within ".protected-branches-list" do
- page.should have_content "stable"
- page.should_not have_content "master"
- end
- end
-
- And 'project "Shop" has protected branches' do
- project = Project.find_by_name("Shop")
- project.protected_branches.create(name: "stable")
- end
-end
diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb
deleted file mode 100644
index 71360fb6bd..0000000000
--- a/features/steps/project/project_browse_files.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-class ProjectBrowseFiles < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- Then 'I should see files from repository' do
- page.should have_content "app"
- page.should have_content "history"
- page.should have_content "Gemfile"
- end
-
- Then 'I should see files from repository for "8470d70"' do
- current_path.should == project_tree_path(@project, "8470d70")
- page.should have_content "app"
- page.should have_content "history"
- page.should have_content "Gemfile"
- end
-
- Given 'I click on "Gemfile.lock" file in repo' do
- click_link "Gemfile.lock"
- end
-
- Then 'I should see it content' do
- page.should have_content "DEPENDENCIES"
- end
-
- And 'I click link "raw"' do
- click_link "raw"
- end
-
- Then 'I should see raw file content' do
- page.source.should == ValidCommit::BLOB_FILE
- end
-
- Given 'I click button "edit"' do
- click_link 'edit'
- end
-
- Then 'I can edit code' do
- page.execute_script('editor.setValue("GitlabFileEditor")')
- page.evaluate_script('editor.getValue()').should == "GitlabFileEditor"
- end
-
-end
diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb
deleted file mode 100644
index 3dc934a611..0000000000
--- a/features/steps/project/project_merge_requests.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-class ProjectMergeRequests < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedNote
- include SharedPaths
-
- Given 'I click link "New Merge Request"' do
- click_link "New Merge Request"
- end
-
- Given 'I click link "Bug NS-04"' do
- click_link "Bug NS-04"
- end
-
- Given 'I click link "All"' do
- click_link "All"
- end
-
- Given 'I click link "Closed"' do
- click_link "Closed"
- end
-
- Then 'I should see merge request "Wiki Feature"' do
- within '.merge-request' do
- page.should have_content "Wiki Feature"
- end
- end
-
- Then 'I should see closed merge request "Bug NS-04"' do
- merge_request = MergeRequest.find_by_title!("Bug NS-04")
- merge_request.closed?.should be_true
- page.should have_content "Closed by"
- end
-
- Then 'I should see merge request "Bug NS-04"' do
- page.should have_content "Bug NS-04"
- end
-
- Then 'I should see "Bug NS-04" in merge requests' do
- page.should have_content "Bug NS-04"
- end
-
- Then 'I should see "Feature NS-03" in merge requests' do
- page.should have_content "Feature NS-03"
- end
-
- And 'I should not see "Feature NS-03" in merge requests' do
- page.should_not have_content "Feature NS-03"
- end
-
-
- And 'I should not see "Bug NS-04" in merge requests' do
- page.should_not have_content "Bug NS-04"
- end
-
- And 'I click link "Close"' do
- click_link "Close"
- end
-
- And 'I submit new merge request "Wiki Feature"' do
- fill_in "merge_request_title", with: "Wiki Feature"
-
- # this must come first, so that the target branch is set
- # by the time the "select" for "notes_refactoring" is executed
- select project.path_with_namespace, from: "merge_request_target_project_id"
- select "master", from: "merge_request_source_branch"
-
- find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
- find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s
-
- # using "notes_refactoring" because "Bug NS-04" uses master/stable,
- # this will fail merge_request validation if the branches are the same
- find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring"
- select "notes_refactoring", from: "merge_request_target_branch"
-
- click_button "Submit merge request"
- end
-
- And 'project "Shop" have "Bug NS-04" open merge request' do
- create(:merge_request,
- title: "Bug NS-04",
- source_project: project,
- target_project: project,
- author: project.users.first)
- end
-
- And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
- create(:merge_request_with_diffs,
- title: "Bug NS-05",
- source_project: project,
- target_project: project,
- author: project.users.first)
- end
-
- And 'project "Shop" have "Feature NS-03" closed merge request' do
- create(:closed_merge_request,
- title: "Feature NS-03",
- source_project: project,
- target_project: project,
- author: project.users.first)
- end
-
- And 'I switch to the diff tab' do
- visit diffs_project_merge_request_path(project, merge_request)
- end
-
- And 'I switch to the merge request\'s comments tab' do
- visit project_merge_request_path(project, merge_request)
- end
-
- And 'I click on the first commit in the merge request' do
- click_link merge_request.commits.first.short_id(8)
- end
-
- And 'I leave a comment on the diff page' do
- init_diff_note
-
- within('.js-temp-notes-holder') do
- fill_in "note_note", with: "One comment to rule them all"
- click_button "Add Comment"
- end
- end
-
- And 'I leave a comment like "Line is wrong" on line 185 of the first file' do
- init_diff_note
-
- within(".js-temp-notes-holder") do
- fill_in "note_note", with: "Line is wrong"
- click_button "Add Comment"
- sleep 0.05
- end
- end
-
- Then 'I should see a discussion has started on line 185' do
- page.should have_content "#{current_user.name} started a discussion on this merge request diff"
- page.should have_content "app/assets/stylesheets/tree.scss:L185"
- page.should have_content "Line is wrong"
- end
-
- Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do
- page.should have_content "#{current_user.name} started a discussion on commit"
- page.should have_content "app/assets/stylesheets/tree.scss:L185"
- page.should have_content "Line is wrong"
- end
-
- Then 'I should see a discussion has started on commit bcf03b5de6c' do
- page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c"
- page.should have_content "One comment to rule them all"
- page.should have_content "app/assets/stylesheets/tree.scss:L185"
- end
-
- def project
- @project ||= Project.find_by_name!("Shop")
- end
-
- def merge_request
- @merge_request ||= MergeRequest.find_by_title!("Bug NS-05")
- end
-
- def init_diff_note
- find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
- end
-end
diff --git a/features/steps/project/project_services.rb b/features/steps/project/project_services.rb
deleted file mode 100644
index 70eafc875d..0000000000
--- a/features/steps/project/project_services.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-class ProjectServices < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- When 'I visit project "Shop" services page' do
- visit project_services_path(@project)
- end
-
- Then 'I should see list of available services' do
- page.should have_content 'Services'
- page.should have_content 'Campfire'
- page.should have_content 'Hipchat'
- page.should have_content 'GitLab CI'
- end
-
- And 'I click gitlab-ci service link' do
- click_link 'GitLab CI'
- end
-
- And 'I fill gitlab-ci settings' do
- check 'Active'
- fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3'
- fill_in 'Token', with: 'verySecret'
- click_button 'Save'
- end
-
- Then 'I should see service settings saved' do
- find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
- end
-
- And 'I click hipchat service link' do
- click_link 'Hipchat'
- end
-
- And 'I fill hipchat settings' do
- check 'Active'
- fill_in 'Room', with: 'gitlab'
- fill_in 'Token', with: 'verySecret'
- click_button 'Save'
- end
-
- Then 'I should see hipchat service settings saved' do
- find_field('Room').value.should == 'gitlab'
- end
-
-
- And 'I click pivotaltracker service link' do
- click_link 'PivotalTracker'
- end
-
- And 'I fill pivotaltracker settings' do
- check 'Active'
- fill_in 'Token', with: 'verySecret'
- click_button 'Save'
- end
-
- Then 'I should see pivotaltracker service settings saved' do
- find_field('Token').value.should == 'verySecret'
- end
-
- And 'I click Flowdock service link' do
- click_link 'Flowdock'
- end
-
- And 'I fill Flowdock settings' do
- check 'Active'
- fill_in 'Token', with: 'verySecret'
- click_button 'Save'
- end
-
- Then 'I should see Flowdock service settings saved' do
- find_field('Token').value.should == 'verySecret'
- end
-end
diff --git a/features/steps/project/public_projects.rb b/features/steps/project/public.rb
similarity index 100%
rename from features/steps/project/public_projects.rb
rename to features/steps/project/public.rb
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
new file mode 100644
index 0000000000..cfa4ce82be
--- /dev/null
+++ b/features/steps/project/redirects.rb
@@ -0,0 +1,35 @@
+class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'public project "Community"' do
+ create :project, :public, name: 'Community'
+ end
+
+ step 'private project "Enterprise"' do
+ create :project, name: 'Enterprise'
+ end
+
+ step 'I visit project "Community" page' do
+ project = Project.find_by(name: 'Community')
+ visit project_path(project)
+ end
+
+ step 'I should see project "Community" home page' do
+ within '.project-home-title' do
+ page.should have_content 'Community'
+ end
+ end
+
+ step 'I visit project "Enterprise" page' do
+ project = Project.find_by(name: 'Enterprise')
+ visit project_path(project)
+ end
+
+ step 'I visit project "CommunityDoesNotExist" page' do
+ project = Project.find_by(name: 'Community')
+ visit project_path(project) + 'DoesNotExist'
+ end
+end
+
diff --git a/features/steps/project/project_search_code.rb b/features/steps/project/search_code.rb
similarity index 100%
rename from features/steps/project/project_search_code.rb
rename to features/steps/project/search_code.rb
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
new file mode 100644
index 0000000000..0594a08a5e
--- /dev/null
+++ b/features/steps/project/services.rb
@@ -0,0 +1,121 @@
+class ProjectServices < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'I visit project "Shop" services page' do
+ visit project_services_path(@project)
+ end
+
+ step 'I should see list of available services' do
+ page.should have_content 'Project services'
+ page.should have_content 'Campfire'
+ page.should have_content 'Hipchat'
+ page.should have_content 'GitLab CI'
+ page.should have_content 'Assembla'
+ end
+
+ step 'I click gitlab-ci service link' do
+ click_link 'GitLab CI'
+ end
+
+ step 'I fill gitlab-ci settings' do
+ check 'Active'
+ fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see service settings saved' do
+ find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
+ end
+
+ step 'I click hipchat service link' do
+ click_link 'Hipchat'
+ end
+
+ step 'I fill hipchat settings' do
+ check 'Active'
+ fill_in 'Room', with: 'gitlab'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see hipchat service settings saved' do
+ find_field('Room').value.should == 'gitlab'
+ end
+
+
+ step 'I click pivotaltracker service link' do
+ click_link 'PivotalTracker'
+ end
+
+ step 'I fill pivotaltracker settings' do
+ check 'Active'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see pivotaltracker service settings saved' do
+ find_field('Token').value.should == 'verySecret'
+ end
+
+ step 'I click Flowdock service link' do
+ click_link 'Flowdock'
+ end
+
+ step 'I fill Flowdock settings' do
+ check 'Active'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Flowdock service settings saved' do
+ find_field('Token').value.should == 'verySecret'
+ end
+
+ step 'I click Assembla service link' do
+ click_link 'Assembla'
+ end
+
+ step 'I fill Assembla settings' do
+ check 'Active'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Assembla service settings saved' do
+ find_field('Token').value.should == 'verySecret'
+ end
+
+ step 'I click email on push service link' do
+ click_link 'Emails on push'
+ end
+
+ step 'I fill email on push settings' do
+ fill_in 'Recipients', with: 'qa@company.name'
+ click_button 'Save'
+ end
+
+ step 'I should see email on push service settings saved' do
+ find_field('Recipients').value.should == 'qa@company.name'
+ end
+
+ step 'I click Slack service link' do
+ click_link 'Slack'
+ end
+
+ step 'I fill Slack settings' do
+ check 'Active'
+ fill_in 'Subdomain', with: 'gitlab'
+ fill_in 'Room', with: '#gitlab'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Slack service settings saved' do
+ find_field('Subdomain').value.should == 'gitlab'
+ find_field('Room').value.should == '#gitlab'
+ find_field('Token').value.should == 'verySecret'
+ end
+end
diff --git a/features/steps/project/project_snippets.rb b/features/steps/project/snippets.rb
similarity index 93%
rename from features/steps/project/project_snippets.rb
rename to features/steps/project/snippets.rb
index 5082b31198..c3a76bea26 100644
--- a/features/steps/project/project_snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -53,7 +53,6 @@ class ProjectSnippets < Spinach::FeatureSteps
And 'I submit new snippet "Snippet three"' do
fill_in "project_snippet_title", :with => "Snippet three"
- select "forever", :from => "project_snippet_expires_at"
fill_in "project_snippet_file_name", :with => "my_snippet.rb"
within('.file-editor') do
find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
@@ -91,10 +90,10 @@ class ProjectSnippets < Spinach::FeatureSteps
end
def project
- @project ||= Project.find_by_name!("Shop")
+ @project ||= Project.find_by!(name: "Shop")
end
def project_snippet
- @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One")
+ @project_snippet ||= ProjectSnippet.find_by!(title: "Snippet one")
end
end
diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/team_management.rb
similarity index 82%
rename from features/steps/project/project_team_management.rb
rename to features/steps/project/team_management.rb
index efebba1be2..ffc5016529 100644
--- a/features/steps/project/project_team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -10,7 +10,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And 'I should see "Sam" in team list' do
- user = User.find_by_name("Sam")
+ user = User.find_by(name: "Sam")
page.should have_content(user.name)
page.should have_content(user.username)
end
@@ -20,7 +20,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And 'I select "Mike" as "Reporter"' do
- user = User.find_by_name("Mike")
+ user = User.find_by(name: "Mike")
select2(user.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
@@ -42,7 +42,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And 'I change "Sam" role to "Reporter"' do
- user = User.find_by_name("Sam")
+ user = User.find_by(name: "Sam")
within "#user_#{user.id}" do
select "Reporter", from: "team_member_project_access"
end
@@ -59,7 +59,7 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And 'I should not see "Sam" in team list' do
- user = User.find_by_name("Sam")
+ user = User.find_by(name: "Sam")
page.should_not have_content(user.name)
page.should_not have_content(user.username)
end
@@ -73,19 +73,19 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
And '"Sam" is "Shop" developer' do
- user = User.find_by_name("Sam")
- project = Project.find_by_name("Shop")
+ user = User.find_by(name: "Sam")
+ project = Project.find_by(name: "Shop")
project.team << [user, :developer]
end
Given 'I own project "Website"' do
- @project = create(:project, name: "Website", namespace: @user.namespace)
+ @project = create(:empty_project, name: "Website", namespace: @user.namespace)
@project.team << [@user, :master]
end
And '"Mike" is "Website" reporter' do
- user = User.find_by_name("Mike")
- project = Project.find_by_name("Website")
+ user = User.find_by(name: "Mike")
+ project = Project.find_by(name: "Website")
project.team << [user, :reporter]
end
@@ -94,13 +94,13 @@ class ProjectTeamManagement < Spinach::FeatureSteps
end
When 'I submit "Website" project for import team' do
- project = Project.find_by_name("Website")
+ project = Project.find_by(name: "Website")
select project.name_with_namespace, from: 'source_project_id'
click_button 'Import'
end
step 'I click cancel link for "Sam"' do
- within "#user_#{User.find_by_name('Sam').id}" do
+ within "#user_#{User.find_by(name: 'Sam').id}" do
click_link('Remove user from team')
end
end
diff --git a/features/steps/project/project_wall.rb b/features/steps/project/wall.rb
similarity index 100%
rename from features/steps/project/project_wall.rb
rename to features/steps/project/wall.rb
diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/wiki.rb
similarity index 54%
rename from features/steps/project/project_wiki.rb
rename to features/steps/project/wiki.rb
index 7aba412d75..96f2505d24 100644
--- a/features/steps/project/project_wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -1,4 +1,4 @@
-class ProjectWiki < Spinach::FeatureSteps
+class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedNote
@@ -16,7 +16,7 @@ class ProjectWiki < Spinach::FeatureSteps
end
Given 'I create the Wiki Home page' do
- fill_in "Content", with: '[link test](test)'
+ fill_in "wiki_content", with: '[link test](test)'
click_on "Create page"
end
@@ -25,7 +25,7 @@ class ProjectWiki < Spinach::FeatureSteps
page.should have_content "link test"
click_link "link test"
- page.should have_content "Editing page"
+ page.should have_content "Editing"
end
Given 'I have an existing Wiki page' do
@@ -83,10 +83,51 @@ class ProjectWiki < Spinach::FeatureSteps
Then 'I should see the existing page in the pages list' do
page.should have_content current_user.name
- page.should have_content @page.title.titleize
+ page.should have_content @page.title
+ end
+
+ Given 'I have an existing Wiki page with images linked on page' do
+ wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ", :markdown, "first commit")
+ @wiki_page = wiki.find_page("pictures")
+ end
+
+ And 'I browse to wiki page with images' do
+ visit project_wiki_path(project, @wiki_page)
+ end
+
+ And 'I click on existing image link' do
+ file = Gollum::File.new(wiki.wiki)
+ Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
+ Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
+ page.should have_link('image', href: "image.jpg")
+ click_on "image"
+ end
+
+ Then 'I should see the image from wiki repo' do
+ url = URI.parse(current_url)
+ url.path.should match("wikis/image.jpg")
+ page.should_not have_xpath('/html') # Page should render the image which means there is no html involved
+ Gollum::Wiki.any_instance.unstub(:file)
+ Gollum::File.any_instance.unstub(:mime_type)
+ end
+
+ Then 'Image should be shown on the page' do
+ page.should have_xpath("//img[@src=\"image.jpg\"]")
+ end
+
+ And 'I click on image link' do
+ page.should have_link('image', href: "image.jpg")
+ click_on "image"
+ end
+
+ Then 'I should see the new wiki page form' do
+ url = URI.parse(current_url)
+ url.path.should match("wikis/image.jpg")
+ page.should have_content('New Wiki Page')
+ page.should have_content('Editing - image.jpg')
end
def wiki
- @gollum_wiki = GollumWiki.new(project, current_user)
+ @project_wiki = ProjectWiki.new(project, current_user)
end
end
diff --git a/features/steps/public/groups_feature.rb b/features/steps/public/groups_feature.rb
new file mode 100644
index 0000000000..015deca542
--- /dev/null
+++ b/features/steps/public/groups_feature.rb
@@ -0,0 +1,93 @@
+class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedGroup
+ include SharedProject
+
+ step 'group "TestGroup" has private project "Enterprise"' do
+ group_has_project("TestGroup", "Enterprise", Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ step 'group "TestGroup" has internal project "Internal"' do
+ group_has_project("TestGroup", "Internal", Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ step 'group "TestGroup" has public project "Community"' do
+ group_has_project("TestGroup", "Community", Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ step '"John Doe" is owner of group "TestGroup"' do
+ group = Group.find_by(name: "TestGroup") || create(:group, name: "TestGroup")
+ user = create(:user, name: "John Doe")
+ group.add_user(user, Gitlab::Access::OWNER)
+ end
+
+ step 'I visit group "TestGroup" page' do
+ visit group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" issues page' do
+ visit issues_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" merge requests page' do
+ visit merge_requests_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I visit group "TestGroup" members page' do
+ visit members_group_path(Group.find_by(name: "TestGroup"))
+ end
+
+ step 'I should not see project "Enterprise" items' do
+ page.should_not have_content "Enterprise"
+ end
+
+ step 'I should see project "Internal" items' do
+ page.should have_content "Internal"
+ end
+
+ step 'I should not see project "Internal" items' do
+ page.should_not have_content "Internal"
+ end
+
+ step 'I should see project "Community" items' do
+ page.should have_content "Community"
+ end
+
+ step 'I change filter to Everyone\'s' do
+ click_link "Everyone's"
+ end
+
+ step 'I should see group member "John Doe"' do
+ page.should have_content "John Doe"
+ end
+
+ step 'I should not see member roles' do
+ page.body.should_not match(%r{owner|developer|reporter|guest}i)
+ end
+
+ protected
+
+ def group_has_project(groupname, projectname, visibility_level)
+ group = Group.find_by(name: groupname) || create(:group, name: groupname)
+ project = create(:project,
+ namespace: group,
+ name: projectname,
+ path: "#{groupname}-#{projectname}",
+ visibility_level: visibility_level
+ )
+ create(:issue,
+ title: "#{projectname} feature",
+ project: project
+ )
+ create(:merge_request,
+ title: "#{projectname} feature implemented",
+ source_project: project,
+ target_project: project
+ )
+ create(:closed_issue_event,
+ project: project
+ )
+ end
+end
+
diff --git a/features/steps/public/projects.rb b/features/steps/public/projects.rb
new file mode 100644
index 0000000000..7c7311bb91
--- /dev/null
+++ b/features/steps/public/projects.rb
@@ -0,0 +1,172 @@
+class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'public empty project "Empty Public Project"' do
+ create :empty_project, :public, name: 'Empty Public Project'
+ end
+
+ step 'I should see project "Empty Public Project"' do
+ page.should have_content "Empty Public Project"
+ end
+
+ step 'I should see public project details' do
+ page.should have_content '32 branches'
+ page.should have_content '16 tags'
+ end
+
+ step 'I should see project readme' do
+ page.should have_content 'README.md'
+ end
+
+ step 'I visit empty project page' do
+ project = Project.find_by(name: 'Empty Public Project')
+ visit project_path(project)
+ end
+
+ step 'I visit project "Community" page' do
+ project = Project.find_by(name: 'Community')
+ visit project_path(project)
+ end
+
+ step 'I should see empty public project details' do
+ page.should have_content 'Git global setup'
+ end
+
+ step 'I should see empty public project details with http clone info' do
+ project = Project.find_by(name: 'Empty Public Project')
+ page.all(:css, '.git-empty .clone').each do |element|
+ element.text.should include(project.http_url_to_repo)
+ end
+ end
+
+ step 'I should see empty public project details with ssh clone info' do
+ project = Project.find_by(name: 'Empty Public Project')
+ page.all(:css, '.git-empty .clone').each do |element|
+ element.text.should include(project.url_to_repo)
+ end
+ end
+
+ step 'I visit project "Enterprise" page' do
+ project = Project.find_by(name: 'Enterprise')
+ visit project_path(project)
+ end
+
+ step 'I should see project "Community" home page' do
+ within '.project-home-title' do
+ page.should have_content 'Community'
+ end
+ end
+
+ step 'I visit project "Internal" page' do
+ project = Project.find_by(name: 'Internal')
+ visit project_path(project)
+ end
+
+ step 'I should see project "Internal" home page' do
+ within '.project-home-title' do
+ page.should have_content 'Internal'
+ end
+ end
+
+ step 'I should see an http link to the repository' do
+ project = Project.find_by(name: 'Community')
+ page.should have_field('project_clone', with: project.http_url_to_repo)
+ end
+
+ step 'I should see an ssh link to the repository' do
+ project = Project.find_by(name: 'Community')
+ page.should have_field('project_clone', with: project.url_to_repo)
+ end
+
+ step 'I visit "Community" issues page' do
+ create(:issue,
+ title: "Bug",
+ project: public_project
+ )
+ create(:issue,
+ title: "New feature",
+ project: public_project
+ )
+ visit project_issues_path(public_project)
+ end
+
+
+ step 'I should see list of issues for "Community" project' do
+ page.should have_content "Bug"
+ page.should have_content public_project.name
+ page.should have_content "New feature"
+ end
+
+ step 'I visit "Internal" issues page' do
+ create(:issue,
+ title: "Internal Bug",
+ project: internal_project
+ )
+ create(:issue,
+ title: "New internal feature",
+ project: internal_project
+ )
+ visit project_issues_path(internal_project)
+ end
+
+
+ step 'I should see list of issues for "Internal" project' do
+ page.should have_content "Internal Bug"
+ page.should have_content internal_project.name
+ page.should have_content "New internal feature"
+ end
+
+ step 'I visit "Community" merge requests page' do
+ visit project_merge_requests_path(public_project)
+ end
+
+ step 'project "Community" has "Bug fix" open merge request' do
+ create(:merge_request,
+ title: "Bug fix for public project",
+ source_project: public_project,
+ target_project: public_project,
+ )
+ end
+
+ step 'I should see list of merge requests for "Community" project' do
+ page.should have_content public_project.name
+ page.should have_content public_merge_request.source_project.name
+ end
+
+ step 'I visit "Internal" merge requests page' do
+ visit project_merge_requests_path(internal_project)
+ end
+
+ step 'project "Internal" has "Feature implemented" open merge request' do
+ create(:merge_request,
+ title: "Feature implemented",
+ source_project: internal_project,
+ target_project: internal_project
+ )
+ end
+
+ step 'I should see list of merge requests for "Internal" project' do
+ page.should have_content internal_project.name
+ page.should have_content internal_merge_request.source_project.name
+ end
+
+ def internal_project
+ @internal_project ||= Project.find_by!(name: 'Internal')
+ end
+
+ def public_project
+ @public_project ||= Project.find_by!(name: 'Community')
+ end
+
+
+ def internal_merge_request
+ @internal_merge_request ||= MergeRequest.find_by!(title: 'Feature implemented')
+ end
+
+ def public_merge_request
+ @public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project')
+ end
+end
+
diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb
deleted file mode 100644
index 8abc6ae9f2..0000000000
--- a/features/steps/public/projects_feature.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
- include SharedPaths
-
- step 'I should see project "Community"' do
- page.should have_content "Community"
- end
-
- step 'I should not see project "Enterprise"' do
- page.should_not have_content "Enterprise"
- end
-
- step 'I should see project "Empty Public Project"' do
- page.should have_content "Empty Public Project"
- end
-
- step 'I should see public project details' do
- page.should have_content '32 branches'
- page.should have_content '16 tags'
- end
-
- step 'I should see project readme' do
- page.should have_content 'README.md'
- end
-
- step 'public project "Community"' do
- create :project_with_code, name: 'Community', public: true, default_branch: 'master'
- end
-
- step 'public empty project "Empty Public Project"' do
- create :project, name: 'Empty Public Project', public: true
- end
-
- step 'I visit empty project page' do
- project = Project.find_by_name('Empty Public Project')
- visit project_path(project)
- end
-
- step 'I visit project "Community" page' do
- project = Project.find_by_name('Community')
- visit project_path(project)
- end
-
- step 'I should see empty public project details' do
- page.should have_content 'Git global setup'
- end
-
- step 'private project "Enterprise"' do
- create :project, name: 'Enterprise'
- end
-
- step 'I should see project "Community" home page' do
- within '.project-home-title' do
- page.should have_content 'Community'
- end
- end
-
- private
-
- def project
- @project ||= Project.find_by_name("Community")
- end
-end
-
diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb
index 8c501bbc53..b8c11ce0a2 100644
--- a/features/steps/shared/authentication.rb
+++ b/features/steps/shared/authentication.rb
@@ -12,6 +12,18 @@ module SharedAuthentication
login_as :admin
end
+ step 'I sign in as "John Doe"' do
+ login_with(user_exists("John Doe"))
+ end
+
+ step 'I sign in as "Mary Jane"' do
+ login_with(user_exists("Mary Jane"))
+ end
+
+ step 'I should be redirected to sign in page' do
+ current_path.should == new_user_session_path
+ end
+
def current_user
@user || User.first
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 9c39a226e1..f80d8d0647 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -2,7 +2,7 @@ module SharedDiffNote
include Spinach::DSL
Given 'I cancel the diff comment' do
- within(".file") do
+ within(".diff-file") do
find(".js-close-discussion-note-form").click
end
end
@@ -13,14 +13,14 @@ module SharedDiffNote
end
Given 'I haven\'t written any diff comment text' do
- within(".file") do
+ within(".diff-file") do
fill_in "note[note]", with: ""
end
end
Given 'I leave a diff comment like "Typo, please fix"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -29,7 +29,7 @@ module SharedDiffNote
Given 'I preview a diff comment text like "Should fix it :smile:"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do
fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click")
end
@@ -38,7 +38,7 @@ module SharedDiffNote
Given 'I preview another diff comment text like "DRY this up"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click
- within(".file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do
+ within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do
fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click")
end
@@ -53,13 +53,13 @@ module SharedDiffNote
end
Given 'I write a diff comment like ":-1: I don\'t like this"' do
- within(".file") do
+ within(".diff-file") do
fill_in "note[note]", with: ":-1: I don\'t like this"
end
end
Given 'I submit the diff comment' do
- within(".file") do
+ within(".diff-file") do
click_button("Add Comment")
end
end
@@ -67,49 +67,49 @@ module SharedDiffNote
Then 'I should not see the diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should_not have_css("form.new_note")
end
end
Then 'I should not see the diff comment preview button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview-button", visible: false)
end
end
Then 'I should not see the diff comment text field' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-text", visible: false)
end
end
Then 'I should only see one diff form' do
- within(".file") do
+ within(".diff-file") do
page.should have_css("form.new_note", count: 1)
end
end
Then 'I should see a diff comment form with ":-1: I don\'t like this"' do
- within(".file") do
+ within(".diff-file") do
page.should have_field("note[note]", with: ":-1: I don\'t like this")
end
end
Then 'I should see a diff comment saying "Typo, please fix"' do
- within(".file .note") do
+ within(".diff-file .note") do
page.should have_content("Typo, please fix")
end
end
Then 'I should see a discussion reply button' do
- within(".file") do
+ within(".diff-file") do
page.should have_link("Reply")
end
end
Then 'I should see a temporary diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-temp-notes-holder form.new_note")
end
end
@@ -119,37 +119,37 @@ module SharedDiffNote
end
Then 'I should see an empty diff comment form' do
- within(".file") do
+ within(".diff-file") do
page.should have_field("note[note]", with: "")
end
end
Then 'I should see the cancel comment button' do
- within(".file form") do
+ within(".diff-file form") do
page.should have_css(".js-close-discussion-note-form", text: "Cancel")
end
end
Then 'I should see the diff comment preview' do
- within(".file form") do
+ within(".diff-file form") do
page.should have_css(".js-note-preview", visible: false)
end
end
Then 'I should see the diff comment edit button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-edit-button", visible: true)
end
end
Then 'I should see the diff comment preview button' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview-button", visible: true)
end
end
Then 'I should see two separate previews' do
- within(".file") do
+ within(".diff-file") do
page.should have_css(".js-note-preview", visible: true, count: 2)
page.should have_content("Should fix it")
page.should have_content("DRY this up")
diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb
new file mode 100644
index 0000000000..6b4c47312a
--- /dev/null
+++ b/features/steps/shared/group.rb
@@ -0,0 +1,36 @@
+module SharedGroup
+ include Spinach::DSL
+
+ step '"John Doe" is owner of group "Owned"' do
+ is_member_of("John Doe", "Owned", Gitlab::Access::OWNER)
+ end
+
+ step '"John Doe" is guest of group "Guest"' do
+ is_member_of("John Doe", "Guest", Gitlab::Access::GUEST)
+ end
+
+ step '"Mary Jane" is owner of group "Owned"' do
+ is_member_of("Mary Jane", "Owned", Gitlab::Access::OWNER)
+ end
+
+ step '"Mary Jane" is guest of group "Owned"' do
+ is_member_of("Mary Jane", "Owned", Gitlab::Access::GUEST)
+ end
+
+ step '"Mary Jane" is guest of group "Guest"' do
+ is_member_of("Mary Jane", "Guest", Gitlab::Access::GUEST)
+ end
+
+ protected
+
+ def is_member_of(username, groupname, role)
+ @project_count ||= 0
+ user = User.find_by(name: username) || create(:user, name: username)
+ group = Group.find_by(name: groupname) || create(:group, name: groupname)
+ group.add_user(user, role)
+ project ||= create(:project, namespace: group, path: "project#{@project_count}")
+ event ||= create(:closed_issue_event, project: project)
+ project.team << [user, :master]
+ @project_count += 1
+ end
+end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
new file mode 100644
index 0000000000..782f3f0920
--- /dev/null
+++ b/features/steps/shared/markdown.rb
@@ -0,0 +1,12 @@
+module SharedMarkdown
+ include Spinach::DSL
+
+ def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki")
+ page.find(:css, "#{parent} h#{level}##{id}").text.should == text
+ page.find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/
+ end
+
+ step 'Header "Description header" should have correct id and link' do
+ header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
+ end
+end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index da08da9420..36b81b7418 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -102,4 +102,21 @@ module SharedNote
page.should have_content("XML attached")
end
end
+
+ # Markdown
+
+ step 'I leave a comment with a header containing "Comment with a header"' do
+ within(".js-main-target-form") do
+ fill_in "note[note]", with: "# Comment with a header"
+ click_button "Add Comment"
+ sleep 0.05
+ end
+ end
+
+ step 'The comment with the header should not have an ID' do
+ within(".note-text") do
+ page.should have_content("Comment with a header")
+ page.should_not have_css("#comment-with-a-header")
+ end
+ end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 156fa5bab4..a0213815a7 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -5,28 +5,56 @@ module SharedPaths
visit new_project_path
end
+ # ----------------------------------------
+ # User
+ # ----------------------------------------
+
+ step 'I visit user "John Doe" page' do
+ visit user_path("john_doe")
+ end
+
# ----------------------------------------
# Group
# ----------------------------------------
- step 'I visit group page' do
- visit group_path(current_group)
+ step 'I visit group "Owned" page' do
+ visit group_path(Group.find_by(name:"Owned"))
end
- step 'I visit group issues page' do
- visit issues_group_path(current_group)
+ step 'I visit group "Owned" issues page' do
+ visit issues_group_path(Group.find_by(name:"Owned"))
end
- step 'I visit group merge requests page' do
- visit merge_requests_group_path(current_group)
+ step 'I visit group "Owned" merge requests page' do
+ visit merge_requests_group_path(Group.find_by(name:"Owned"))
end
- step 'I visit group members page' do
- visit members_group_path(current_group)
+ step 'I visit group "Owned" members page' do
+ visit members_group_path(Group.find_by(name:"Owned"))
end
- step 'I visit group settings page' do
- visit edit_group_path(current_group)
+ step 'I visit group "Owned" settings page' do
+ visit edit_group_path(Group.find_by(name:"Owned"))
+ end
+
+ step 'I visit group "Guest" page' do
+ visit group_path(Group.find_by(name:"Guest"))
+ end
+
+ step 'I visit group "Guest" issues page' do
+ visit issues_group_path(Group.find_by(name:"Guest"))
+ end
+
+ step 'I visit group "Guest" merge requests page' do
+ visit merge_requests_group_path(Group.find_by(name:"Guest"))
+ end
+
+ step 'I visit group "Guest" members page' do
+ visit members_group_path(Group.find_by(name:"Guest"))
+ end
+
+ step 'I visit group "Guest" settings page' do
+ visit edit_group_path(Group.find_by(name:"Guest"))
end
# ----------------------------------------
@@ -85,6 +113,14 @@ module SharedPaths
visit history_profile_path
end
+ step 'I visit profile groups page' do
+ visit profile_groups_path
+ end
+
+ step 'I should be redirected to the profile groups page' do
+ current_path.should == profile_groups_path
+ end
+
# ----------------------------------------
# Admin
# ----------------------------------------
@@ -105,6 +141,10 @@ module SharedPaths
visit admin_logs_path
end
+ step 'I visit admin messages page' do
+ visit admin_broadcast_messages_path
+ end
+
step 'I visit admin hooks page' do
visit admin_hooks_path
end
@@ -237,7 +277,7 @@ module SharedPaths
end
step 'I visit issue page "Release 0.4"' do
- issue = Issue.find_by_title("Release 0.4")
+ issue = Issue.find_by(title: "Release 0.4")
visit project_issue_path(issue.project, issue)
end
@@ -246,12 +286,12 @@ module SharedPaths
end
step 'I visit merge request page "Bug NS-04"' do
- mr = MergeRequest.find_by_title("Bug NS-04")
+ mr = MergeRequest.find_by(title: "Bug NS-04")
visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit merge request page "Bug NS-05"' do
- mr = MergeRequest.find_by_title("Bug NS-05")
+ mr = MergeRequest.find_by(title: "Bug NS-05")
visit project_merge_request_path(mr.target_project, mr)
end
@@ -288,7 +328,7 @@ module SharedPaths
end
step 'I visit public page for "Community" project' do
- visit public_project_path(Project.find_by_name("Community"))
+ visit public_project_path(Project.find_by(name: "Community"))
end
# ----------------------------------------
@@ -312,6 +352,14 @@ module SharedPaths
end
def project
- project = Project.find_by_name!("Shop")
+ project = Project.find_by!(name: "Shop")
+ end
+
+ # ----------------------------------------
+ # Errors
+ # ----------------------------------------
+
+ Then 'page status code should be 404' do
+ page.status_code.should == 404
end
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index cef66b038d..f8cb753b78 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -3,19 +3,26 @@ module SharedProject
# Create a project without caring about what it's called
And "I own a project" do
- @project = create(:project_with_code, namespace: @user.namespace)
+ @project = create(:project, namespace: @user.namespace)
@project.team << [@user, :master]
end
# Create a specific project called "Shop"
And 'I own project "Shop"' do
- @project = Project.find_by_name "Shop"
- @project ||= create(:project_with_code, name: "Shop", namespace: @user.namespace)
+ @project = Project.find_by(name: "Shop")
+ @project ||= create(:project, name: "Shop", namespace: @user.namespace)
+ @project.team << [@user, :master]
+ end
+
+ # Create another specific project called "Forum"
+ And 'I own project "Forum"' do
+ @project = Project.find_by(name: "Forum")
+ @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project')
@project.team << [@user, :master]
end
And 'project "Shop" has push event' do
- @project = Project.find_by_name("Shop")
+ @project = Project.find_by(name: "Shop")
data = {
before: "0000000000000000000000000000000000000000",
@@ -41,7 +48,7 @@ module SharedProject
end
Then 'I should see project "Shop" activity feed' do
- project = Project.find_by_name("Shop")
+ project = Project.find_by(name: "Shop")
page.should have_content "#{@user.name} pushed new branch new_design at #{project.name_with_namespace}"
end
@@ -51,11 +58,68 @@ module SharedProject
page.should have_content("Features:")
end
- Then 'page status code should be 404' do
- page.status_code.should == 404
- end
-
def current_project
@project ||= Project.first
end
+
+ # ----------------------------------------
+ # Visibility level
+ # ----------------------------------------
+
+ step 'private project "Enterprise"' do
+ create :project, name: 'Enterprise'
+ end
+
+ step 'I should see project "Enterprise"' do
+ page.should have_content "Enterprise"
+ end
+
+ step 'I should not see project "Enterprise"' do
+ page.should_not have_content "Enterprise"
+ end
+
+ step 'internal project "Internal"' do
+ create :project, :internal, name: 'Internal'
+ end
+
+ step 'I should see project "Internal"' do
+ page.should have_content "Internal"
+ end
+
+ step 'I should not see project "Internal"' do
+ page.should_not have_content "Internal"
+ end
+
+ step 'public project "Community"' do
+ create :project, :public, name: 'Community'
+ end
+
+ step 'I should see project "Community"' do
+ page.should have_content "Community"
+ end
+
+ step 'I should not see project "Community"' do
+ page.should_not have_content "Community"
+ end
+
+ step '"John Doe" is authorized to private project "Enterprise"' do
+ user = user_exists("John Doe", username: "john_doe")
+ project = Project.find_by(name: "Enterprise")
+ project ||= create(:project, name: "Enterprise", namespace: user.namespace)
+ project.team << [user, :master]
+ end
+
+ step '"John Doe" is authorized to internal project "Internal"' do
+ user = user_exists("John Doe", username: "john_doe")
+ project = Project.find_by(name: "Internal")
+ project ||= create :project, :internal, name: 'Internal'
+ project.team << [user, :master]
+ end
+
+ step '"John Doe" is authorized to public project "Community"' do
+ user = user_exists("John Doe", username: "john_doe")
+ project = Project.find_by(name: "Community")
+ project ||= create :project, :public, name: 'Community'
+ project.team << [user, :master]
+ end
end
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
new file mode 100644
index 0000000000..209d77c7ac
--- /dev/null
+++ b/features/steps/shared/user.rb
@@ -0,0 +1,17 @@
+module SharedUser
+ include Spinach::DSL
+
+ step 'User "John Doe" exists' do
+ user_exists("John Doe", {username: "john_doe"})
+ end
+
+ step 'User "Mary Jane" exists' do
+ user_exists("Mary Jane", {username: "mary_jane"})
+ end
+
+ protected
+
+ def user_exists(name, options = {})
+ User.find_by(name: name) || create(:user, {name: name, admin: false}.merge(options))
+ end
+end
diff --git a/features/steps/snippets/discover_snippets.rb b/features/steps/snippets/discover.rb
similarity index 84%
rename from features/steps/snippets/discover_snippets.rb
rename to features/steps/snippets/discover.rb
index 3afe019adf..0933793700 100644
--- a/features/steps/snippets/discover_snippets.rb
+++ b/features/steps/snippets/discover.rb
@@ -12,6 +12,6 @@ class DiscoverSnippets < Spinach::FeatureSteps
end
def snippet
- @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index bbdf5b97c8..fed54659eb 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -19,7 +19,7 @@ class SnippetsFeature < Spinach::FeatureSteps
end
And 'I click link "Destroy"' do
- click_link "Destroy"
+ click_link "Remove"
end
And 'I submit new snippet "Personal snippet three"' do
@@ -46,7 +46,7 @@ class SnippetsFeature < Spinach::FeatureSteps
end
And 'I uncheck "Private" checkbox' do
- find(:xpath, "//input[@id='personal_snippet_private']").set true
+ choose "Public"
click_button "Save"
end
@@ -59,6 +59,6 @@ class SnippetsFeature < Spinach::FeatureSteps
end
def snippet
- @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
end
diff --git a/features/steps/snippets/user_snippets.rb b/features/steps/snippets/user.rb
similarity index 92%
rename from features/steps/snippets/user_snippets.rb
rename to features/steps/snippets/user.rb
index 15d6da6db3..2d7ffc866e 100644
--- a/features/steps/snippets/user_snippets.rb
+++ b/features/steps/snippets/user.rb
@@ -36,6 +36,6 @@ class UserSnippets < Spinach::FeatureSteps
end
def snippet
- @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one")
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
end
diff --git a/features/steps/user.rb b/features/steps/user.rb
new file mode 100644
index 0000000000..5fb248ffcb
--- /dev/null
+++ b/features/steps/user.rb
@@ -0,0 +1,10 @@
+class Spinach::Features::User < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedUser
+ include SharedProject
+
+ step 'I should see user "John Doe" page' do
+ expect(page.title).to match(/^\s*John Doe/)
+ end
+end
diff --git a/features/support/env.rb b/features/support/env.rb
index d27a73edab..a5b297775d 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -9,12 +9,13 @@ ENV['RAILS_ENV'] = 'test'
require './config/environment'
require 'rspec'
+require 'rspec/expectations'
require 'database_cleaner'
require 'spinach/capybara'
require 'sidekiq/testing/inline'
-%w(valid_commit big_commits select2_helper chosen_helper test_env).each do |f|
+%w(valid_commit valid_commit_with_alt_email big_commits select2_helper test_env).each do |f|
require Rails.root.join('spec', 'support', f)
end
@@ -51,6 +52,4 @@ Spinach.hooks.before_run do
RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods
- MergeRequestObserver.any_instance.stub(current_user: create(:user))
end
-
diff --git a/features/user.feature b/features/user.feature
new file mode 100644
index 0000000000..d4198c08de
--- /dev/null
+++ b/features/user.feature
@@ -0,0 +1,69 @@
+Feature: User
+ Background:
+ Given User "John Doe" exists
+ And "John Doe" is authorized to private project "Enterprise"
+
+ # Signed out
+
+ Scenario: I visit user "John Doe" page while not signed in when he is authorized to a public project
+ Given "John Doe" is authorized to internal project "Internal"
+ And "John Doe" is authorized to public project "Community"
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should not see project "Enterprise"
+ And I should not see project "Internal"
+ And I should see project "Community"
+
+ Scenario: I visit user "John Doe" page while not signed in when he is not authorized to a public project
+ Given "John Doe" is authorized to internal project "Internal"
+ When I visit user "John Doe" page
+ Then I should be redirected to sign in page
+
+ # Signed in as someone else
+
+ Scenario: I visit user "John Doe" page while signed in as someone else when he is authorized to a public project
+ Given "John Doe" is authorized to public project "Community"
+ And "John Doe" is authorized to internal project "Internal"
+ And I sign in as a user
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should not see project "Enterprise"
+ And I should see project "Internal"
+ And I should see project "Community"
+
+ Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a public project
+ Given "John Doe" is authorized to internal project "Internal"
+ And I sign in as a user
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should not see project "Enterprise"
+ And I should see project "Internal"
+ And I should not see project "Community"
+
+ Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a project I can see
+ Given I sign in as a user
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should not see project "Enterprise"
+ And I should not see project "Internal"
+ And I should not see project "Community"
+
+ # Signed in as the user himself
+
+ Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has a public project
+ Given "John Doe" is authorized to internal project "Internal"
+ And "John Doe" is authorized to public project "Community"
+ And I sign in as "John Doe"
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should see project "Enterprise"
+ And I should see project "Internal"
+ And I should see project "Community"
+
+ Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has no public project
+ Given I sign in as "John Doe"
+ When I visit user "John Doe" page
+ Then I should see user "John Doe" page
+ And I should see project "Enterprise"
+ And I should not see project "Internal"
+ And I should not see project "Community"
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 4db81f42b4..ce4cc8b34f 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -22,6 +22,8 @@ module API
end
format :json
+ content_type :txt, "text/plain"
+
helpers APIHelpers
mount Groups
@@ -36,8 +38,13 @@ module API
mount Internal
mount SystemHooks
mount ProjectSnippets
+ mount ProjectMembers
mount DeployKeys
mount ProjectHooks
mount Services
+ mount Files
+ mount Commits
+ mount Namespaces
+ mount Branches
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
new file mode 100644
index 0000000000..d54f9371fb
--- /dev/null
+++ b/lib/api/branches.rb
@@ -0,0 +1,89 @@
+require 'mime/types'
+
+module API
+ # Projects API
+ class Branches < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ resource :projects do
+ # Get a project repository branches
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/branches
+ get ":id/repository/branches" do
+ present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # GET /projects/:id/repository/branches/:branch
+ get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found!("Branch does not exist") if @branch.nil?
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Protect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/protect
+ put ':id/repository/branches/:branch/protect',
+ requirements: { branch: /.*/ } do
+
+ authorize_admin_project
+
+ @branch = user_project.repository.find_branch(params[:branch])
+ not_found! unless @branch
+ protected_branch = user_project.protected_branches.find_by(name: @branch.name)
+ user_project.protected_branches.create(name: @branch.name) unless protected_branch
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Unprotect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/unprotect
+ put ':id/repository/branches/:branch/unprotect',
+ requirements: { branch: /.*/ } do
+
+ authorize_admin_project
+
+ @branch = user_project.repository.find_branch(params[:branch])
+ not_found! unless @branch
+ protected_branch = user_project.protected_branches.find_by(name: @branch.name)
+ protected_branch.destroy if protected_branch
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Create branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch_name (required) - The name of the branch
+ # ref (required) - Create branch from commit sha or existing branch
+ # Example Request:
+ # POST /projects/:id/repository/branches
+ post ":id/repository/branches" do
+ authorize_push_project
+ @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user)
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+ end
+ end
+end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
new file mode 100644
index 0000000000..4a67313430
--- /dev/null
+++ b/lib/api/commits.rb
@@ -0,0 +1,55 @@
+require 'mime/types'
+
+module API
+ # Projects commits API
+ class Commits < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ resource :projects do
+ # Get a project repository commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/commits
+ get ":id/repository/commits" do
+ page = (params[:page] || 0).to_i
+ per_page = (params[:per_page] || 20).to_i
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+
+ commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ present commits, with: Entities::RepoCommit
+ end
+
+ # Get a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash or name of a repository branch or tag
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha
+ get ":id/repository/commits/:sha" do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ present commit, with: Entities::RepoCommitDetail
+ end
+
+ # Get the diff for a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha/diff
+ get ":id/repository/commits/:sha/diff" do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ commit.diffs
+ end
+ end
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index b599760899..7f5a125038 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -38,14 +38,14 @@ module API
attrs[:key].strip!
# check if key already exist in project
- key = user_project.deploy_keys.find_by_key(attrs[:key])
+ key = user_project.deploy_keys.find_by(key: attrs[:key])
if key
present key, with: Entities::SSHKey
return
end
# Check for available deploy keys in other projects
- key = current_user.accessible_deploy_keys.find_by_key(attrs[:key])
+ key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
if key
user_project.deploy_keys << key
present key, with: Entities::SSHKey
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 429083d75b..457af52fe9 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,15 +1,21 @@
module API
module Entities
class User < Grape::Entity
- expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
+ expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, :website_url,
:theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider
expose :is_admin?, as: :is_admin
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
+
+ expose :avatar_url do |user, options|
+ if user.avatar.present?
+ user.avatar.url
+ end
+ end
end
class UserSafe < Grape::Entity
- expose :name
+ expose :name, :username
end
class UserBasic < Grape::Entity
@@ -24,6 +30,10 @@ module API
expose :id, :url, :created_at
end
+ class ProjectHook < Hook
+ expose :project_id, :push_events, :issues_events, :merge_requests_events
+ end
+
class ForkedFromProject < Grape::Entity
expose :id
expose :name, :name_with_namespace
@@ -31,30 +41,21 @@ module API
end
class Project < Grape::Entity
- expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
- expose :owner, using: Entities::UserBasic
+ expose :id, :description, :default_branch
+ expose :public?, as: :public
+ expose :archived?, as: :archived
+ expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
+ expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public
+ expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
end
class ProjectMember < UserBasic
expose :project_access, as: :access_level do |user, options|
- options[:project].users_projects.find_by_user_id(user.id).project_access
- end
- end
-
- class TeamMember < UserBasic
- expose :permission, as: :access_level do |user, options|
- options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission
- end
- end
-
- class TeamProject < Project
- expose :greatest_access, as: :greatest_access_level do |project, options|
- options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access
+ options[:project].users_projects.find_by(user_id: user.id).project_access
end
end
@@ -68,12 +69,21 @@ module API
class GroupMember < UserBasic
expose :group_access, as: :access_level do |user, options|
- options[:group].users_groups.find_by_user_id(user.id).group_access
+ options[:group].users_groups.find_by(user_id: user.id).group_access
end
end
class RepoObject < Grape::Entity
- expose :name, :commit
+ expose :name
+
+ expose :commit do |repo_obj, options|
+ if repo_obj.respond_to?(:commit)
+ repo_obj.commit
+ elsif options[:project]
+ options[:project].repository.commit(repo_obj.target)
+ end
+ end
+
expose :protected do |repo, options|
if options[:project]
options[:project].protected_branch? repo.name
@@ -81,10 +91,24 @@ module API
end
end
+ class RepoTreeObject < Grape::Entity
+ expose :id, :name, :type
+
+ expose :mode do |obj, options|
+ filemode = obj.mode.to_s(8)
+ filemode = "0" + filemode if filemode.length < 6
+ filemode
+ end
+ end
+
class RepoCommit < Grape::Entity
expose :id, :short_id, :title, :author_name, :author_email, :created_at
end
+ class RepoCommitDetail < RepoCommit
+ expose :parent_ids, :committed_date, :authored_date
+ end
+
class ProjectSnippet < Grape::Entity
expose :id, :title, :file_name
expose :author, using: Entities::UserBasic
@@ -94,24 +118,25 @@ module API
class ProjectEntity < Grape::Entity
expose :id, :iid
expose (:project_id) { |entity| entity.project.id }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
end
class Milestone < ProjectEntity
- expose :title, :description, :due_date, :state, :updated_at, :created_at
+ expose :due_date
end
class Issue < ProjectEntity
- expose :title, :description
expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :state, :updated_at, :created_at
end
class MergeRequest < ProjectEntity
- expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes
+ expose :target_branch, :source_branch, :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
+ expose :label_list, as: :labels
end
class SSHKey < Grape::Entity
@@ -135,6 +160,39 @@ module API
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
expose :data, :target_title
+ expose :created_at
+ end
+
+ class Namespace < Grape::Entity
+ expose :id, :path, :kind
+ end
+
+ class ProjectAccess < Grape::Entity
+ expose :project_access, as: :access_level
+ expose :notification_level
+ end
+
+ class GroupAccess < Grape::Entity
+ expose :group_access, as: :access_level
+ expose :notification_level
+ end
+
+ class ProjectWithAccess < Project
+ expose :permissions do
+ expose :project_access, using: Entities::ProjectAccess do |project, options|
+ project.users_projects.find_by(user_id: options[:user].id)
+ end
+
+ expose :group_access, using: Entities::GroupAccess do |project, options|
+ if project.group
+ project.group.users_groups.find_by(user_id: options[:user].id)
+ end
+ end
+ end
+ end
+
+ class Label < Grape::Entity
+ expose :name
end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
new file mode 100644
index 0000000000..e0c46f92b8
--- /dev/null
+++ b/lib/api/files.rb
@@ -0,0 +1,150 @@
+module API
+ # Projects API
+ class Files < Grape::API
+ before { authenticate! }
+ before { authorize! :push_code, user_project }
+
+ resource :projects do
+ # Get file from repository
+ # File content is Base64 encoded
+ #
+ # Parameters:
+ # file_path (required) - The path to the file. Ex. lib/class.rb
+ # ref (required) - The name of branch, tag or commit
+ #
+ # Example Request:
+ # GET /projects/:id/repository/files
+ #
+ # Example response:
+ # {
+ # "file_name": "key.rb",
+ # "file_path": "app/models/key.rb",
+ # "size": 1476,
+ # "encoding": "base64",
+ # "content": "IyA9PSBTY2hlbWEgSW5mb3...",
+ # "ref": "master",
+ # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
+ # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50"
+ # }
+ #
+ get ":id/repository/files" do
+ required_attributes! [:file_path, :ref]
+ attrs = attributes_for_keys [:file_path, :ref]
+ ref = attrs.delete(:ref)
+ file_path = attrs.delete(:file_path)
+
+ commit = user_project.repository.commit(ref)
+ not_found! "Commit" unless commit
+
+ blob = user_project.repository.blob_at(commit.sha, file_path)
+
+ if blob
+ status(200)
+
+ {
+ file_name: blob.name,
+ file_path: blob.path,
+ size: blob.size,
+ encoding: "base64",
+ content: Base64.encode64(blob.data),
+ ref: ref,
+ blob_id: blob.id,
+ commit_id: commit.id,
+ }
+ else
+ render_api_error!('File not found', 404)
+ end
+ end
+
+ # Create new file in repository
+ #
+ # Parameters:
+ # file_path (required) - The path to new file. Ex. lib/class.rb
+ # branch_name (required) - The name of branch
+ # content (required) - File content
+ # commit_message (required) - Commit message
+ #
+ # Example Request:
+ # POST /projects/:id/repository/files
+ #
+ post ":id/repository/files" do
+ required_attributes! [:file_path, :branch_name, :content, :commit_message]
+ attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
+ branch_name = attrs.delete(:branch_name)
+ file_path = attrs.delete(:file_path)
+ result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+
+ if result[:status] == :success
+ status(201)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
+ else
+ render_api_error!(result[:error], 400)
+ end
+ end
+
+ # Update existing file in repository
+ #
+ # Parameters:
+ # file_path (optional) - The path to file. Ex. lib/class.rb
+ # branch_name (required) - The name of branch
+ # content (required) - File content
+ # commit_message (required) - Commit message
+ #
+ # Example Request:
+ # PUT /projects/:id/repository/files
+ #
+ put ":id/repository/files" do
+ required_attributes! [:file_path, :branch_name, :content, :commit_message]
+ attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
+ branch_name = attrs.delete(:branch_name)
+ file_path = attrs.delete(:file_path)
+ result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+
+ if result[:status] == :success
+ status(200)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
+ else
+ render_api_error!(result[:error], 400)
+ end
+ end
+
+ # Delete existing file in repository
+ #
+ # Parameters:
+ # file_path (optional) - The path to file. Ex. lib/class.rb
+ # branch_name (required) - The name of branch
+ # content (required) - File content
+ # commit_message (required) - Commit message
+ #
+ # Example Request:
+ # DELETE /projects/:id/repository/files
+ #
+ delete ":id/repository/files" do
+ required_attributes! [:file_path, :branch_name, :commit_message]
+ attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
+ branch_name = attrs.delete(:branch_name)
+ file_path = attrs.delete(:file_path)
+ result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
+
+ if result[:status] == :success
+ status(200)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
+ else
+ render_api_error!(result[:error], 400)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 290b78d801..03f027706d 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -121,11 +121,11 @@ module API
render_api_error!("Wrong access level", 422)
end
group = find_group(params[:id])
- if group.users_groups.find_by_user_id(params[:user_id])
+ if group.users_groups.find_by(user_id: params[:user_id])
render_api_error!("Already exists", 409)
end
group.add_users([params[:user_id]], params[:access_level])
- member = group.users_groups.find_by_user_id(params[:user_id])
+ member = group.users_groups.find_by(user_id: params[:user_id])
present member.user, with: Entities::GroupMember, group: group
end
@@ -139,7 +139,7 @@ module API
# DELETE /groups/:id/members/:user_id
delete ":id/members/:user_id" do
group = find_group(params[:id])
- member = group.users_groups.find_by_user_id(params[:user_id])
+ member = group.users_groups.find_by(user_id: params[:user_id])
if member.nil?
render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
else
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index edc662eaaa..654c1f62c6 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -6,19 +6,28 @@ module API
SUDO_PARAM = :sudo
def current_user
- @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
+ private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
+ @current_user ||= User.find_by(authentication_token: private_token)
+
+ unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
+ return nil
+ end
+
identifier = sudo_identifier()
+
# If the sudo is the current user do nothing
if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
@current_user = User.by_username_or_id(identifier)
not_found!("No user id or username for: #{identifier}") if @current_user.nil?
end
+
@current_user
end
def sudo_identifier()
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+
# Regex for integers
if (!!(identifier =~ /^[0-9]+$/))
identifier.to_i
@@ -29,6 +38,7 @@ module API
def set_current_user_for_thread
Thread.current[:current_user] = current_user
+
begin
yield
ensure
@@ -42,7 +52,7 @@ module API
end
def find_project(id)
- project = Project.find_by_id(id) || Project.find_with_namespace(id)
+ project = Project.find_with_namespace(id) || Project.find_by(id: id)
if project && can?(current_user, :read_project, project)
project
@@ -51,8 +61,12 @@ module API
end
end
- def paginate(object)
- object.page(params[:page]).per(params[:per_page].to_i)
+ def paginate(relation)
+ per_page = params[:per_page].to_i
+ paginated = relation.page(params[:page]).per(per_page)
+ add_pagination_headers(paginated, per_page)
+
+ paginated
end
def authenticate!
@@ -69,6 +83,10 @@ module API
end
end
+ def authorize_push_project
+ authorize! :push_code, user_project
+ end
+
def authorize_admin_project
authorize! :admin_project, user_project
end
@@ -129,6 +147,18 @@ module API
private
+ def add_pagination_headers(paginated, per_page)
+ request_url = request.url.split('?').first
+
+ links = []
+ links << %(<#{request_url}?page=#{paginated.current_page - 1}&per_page=#{per_page}>; rel="prev") unless paginated.first_page?
+ links << %(<#{request_url}?page=#{paginated.current_page + 1}&per_page=#{per_page}>; rel="next") unless paginated.last_page?
+ links << %(<#{request_url}?page=1&per_page=#{per_page}>; rel="first")
+ links << %(<#{request_url}?page=#{paginated.total_pages}&per_page=#{per_page}>; rel="last")
+
+ header 'Link', links.join(', ')
+ end
+
def abilities
@abilities ||= begin
abilities = Six.new
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index ed6b50c3a6..06c66ba0b3 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,19 +1,16 @@
module API
# Internal access API
class Internal < Grape::API
-
- DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
- PUSH_COMMANDS = %w{ git-receive-pack }
-
namespace 'internal' do
- #
- # Check if ssh key has access to project code
+ # Check if git command is allowed to project
#
# Params:
- # key_id - SSH Key id
+ # key_id - ssh key id for Git over SSH
+ # user_id - user id for Git over HTTP
# project - project path with namespace
# action - git action (git-upload-pack or git-receive-pack)
# ref - branch name
+ # forced_push - forced_push
#
get "/allowed" do
# Check for *.wiki repositories.
@@ -22,35 +19,26 @@ module API
# the wiki repository as well.
project_path = params[:project]
project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
-
- key = Key.find(params[:key_id])
project = Project.find_with_namespace(project_path)
- git_cmd = params[:action]
return false unless project
+ actor = if params[:key_id]
+ Key.find(params[:key_id])
+ elsif params[:user_id]
+ User.find(params[:user_id])
+ end
- if key.is_a? DeployKey
- key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd)
- else
- user = key.user
+ return false unless actor
- return false if user.blocked?
- return false if user.ldap_user? && Gitlab::LDAP::User.blocked?(user.extern_uid)
-
- action = case git_cmd
- when *DOWNLOAD_COMMANDS
- then :download_code
- when *PUSH_COMMANDS
- then
- if project.protected_branch?(params[:ref])
- :push_code_to_protected_branches
- else
- :push_code
- end
- end
-
- user.can?(action, project)
- end
+ Gitlab::GitAccess.new.allowed?(
+ actor,
+ params[:action],
+ project,
+ params[:ref],
+ params[:oldrev],
+ params[:newrev],
+ params[:forced_push]
+ )
end
#
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 3d15c35b8c..f50be3a815 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -48,17 +48,15 @@ module API
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
- set_current_user_for_thread do
- required_attributes! [:title]
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
- attrs[:label_list] = params[:labels] if params[:labels].present?
- @issue = user_project.issues.new attrs
- @issue.author = current_user
- if @issue.save
- present @issue, with: Entities::Issue
- else
- not_found!
- end
+ required_attributes! [:title]
+ attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
+ attrs[:label_list] = params[:labels] if params[:labels].present?
+ issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
+
+ if issue.valid?
+ present issue, with: Entities::Issue
+ else
+ not_found!
end
end
@@ -76,18 +74,18 @@ module API
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
- set_current_user_for_thread do
- @issue = user_project.issues.find(params[:issue_id])
- authorize! :modify_issue, @issue
+ issue = user_project.issues.find(params[:issue_id])
+ authorize! :modify_issue, issue
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
- attrs[:label_list] = params[:labels] if params[:labels].present?
+ attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
+ attrs[:label_list] = params[:labels] if params[:labels].present?
- if @issue.update_attributes attrs
- present @issue, with: Entities::Issue
- else
- not_found!
- end
+ issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
+
+ if issue.valid?
+ present issue, with: Entities::Issue
+ else
+ not_found!
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 3f4bec895b..017cb1f562 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -13,28 +13,30 @@ module API
end
not_found!
end
-
- def not_fork?(target_project_id, user_project)
- target_project_id.nil? || target_project_id == user_project.id.to_s
- end
-
- def target_matches_fork(target_project_id,user_project)
- user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
- end
end
# List merge requests
#
# Parameters:
# id (required) - The ID of a project
+ # state (optional) - Return requests "merged", "opened" or "closed"
#
# Example:
# GET /projects/:id/merge_requests
+ # GET /projects/:id/merge_requests?state=opened
+ # GET /projects/:id/merge_requests?state=closed
#
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
- present paginate(user_project.merge_requests), with: Entities::MergeRequest
+ mrs = case params["state"]
+ when "opened" then user_project.merge_requests.opened
+ when "closed" then user_project.merge_requests.closed
+ when "merged" then user_project.merge_requests.merged
+ else user_project.merge_requests
+ end
+
+ present paginate(mrs), with: Entities::MergeRequest
end
# Show MR
@@ -64,35 +66,23 @@ module API
# target_project - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
+ # description - Description of MR
+ # labels (optional) - Labels for MR as a comma-separated list
#
# Example:
# POST /projects/:id/merge_requests
#
post ":id/merge_requests" do
- set_current_user_for_thread do
- authorize! :write_merge_request, user_project
- required_attributes! [:source_branch, :target_branch, :title]
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
- merge_request = user_project.merge_requests.new(attrs)
- merge_request.author = current_user
- merge_request.source_project = user_project
- target_project_id = attrs[:target_project_id]
- if not_fork?(target_project_id, user_project)
- merge_request.target_project = user_project
- else
- if target_matches_fork(target_project_id,user_project)
- merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
- else
- render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
- end
- end
+ authorize! :write_merge_request, user_project
+ required_attributes! [:source_branch, :target_branch, :title]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
+ attrs[:label_list] = params[:labels] if params[:labels].present?
+ merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
- if merge_request.save
- merge_request.reload_code
- present merge_request, with: Entities::MergeRequest
- else
- handle_merge_request_errors! merge_request.errors
- end
+ if merge_request.valid?
+ present merge_request, with: Entities::MergeRequest
+ else
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -106,26 +96,84 @@ module API
# assignee_id - Assignee user ID
# title - Title of MR
# state_event - Status of MR. (close|reopen|merge)
+ # description - Description of MR
+ # labels (optional) - Labels for a MR as a comma-separated list
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- set_current_user_for_thread do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
+ attrs[:label_list] = params[:labels] if params[:labels].present?
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ authorize! :modify_merge_request, merge_request
+ merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
- authorize! :modify_merge_request, merge_request
-
- if merge_request.update_attributes attrs
- merge_request.reload_code
- merge_request.mark_as_unchecked
- present merge_request, with: Entities::MergeRequest
- else
- handle_merge_request_errors! merge_request.errors
- end
+ if merge_request.valid?
+ present merge_request, with: Entities::MergeRequest
+ else
+ handle_merge_request_errors! merge_request.errors
end
end
+ # Merge MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # merge_commit_message (optional) - Custom merge commit message
+ # Example:
+ # PUT /projects/:id/merge_request/:merge_request_id/merge
+ #
+ put ":id/merge_request/:merge_request_id/merge" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ action = if user_project.protected_branch?(merge_request.target_branch)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+
+ if can?(current_user, action, user_project)
+ if merge_request.unchecked?
+ merge_request.check_if_can_be_merged
+ end
+
+ if merge_request.open?
+ if merge_request.can_be_merged?
+ merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message)
+ present merge_request, with: Entities::MergeRequest
+ else
+ render_api_error!('Branch cannot be merged', 405)
+ end
+ else
+ # Merge request can not be merged
+ # because it is already closed/merged
+ not_allowed!
+ end
+ else
+ # Merge request can not be merged
+ # because user dont have permissions to push into target branch
+ unauthorized!
+ end
+ end
+
+
+ # Get a merge request's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # Examples:
+ # GET /projects/:id/merge_request/:merge_request_id/comments
+ #
+ get ":id/merge_request/:merge_request_id/comments" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :read_merge_request, merge_request
+
+ present paginate(merge_request.notes), with: Entities::MRNote
+ end
+
# Post comment to merge request
#
# Parameters:
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
new file mode 100644
index 0000000000..f9f2ed90cc
--- /dev/null
+++ b/lib/api/namespaces.rb
@@ -0,0 +1,23 @@
+module API
+ # namespaces API
+ class Namespaces < Grape::API
+ before {
+ authenticate!
+ authenticated_as_admin!
+ }
+
+ resource :namespaces do
+ # Get a namespaces list
+ #
+ # Example Request:
+ # GET /namespaces
+ get do
+ @namespaces = Namespace.all
+ @namespaces = @namespaces.search(params[:search]) if params[:search].present?
+ @namespaces = paginate @namespaces
+
+ present @namespaces, with: Entities::Namespace
+ end
+ end
+ end
+end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 738974955f..79c3d122d3 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -5,15 +5,6 @@ module API
before { authorize_admin_project }
resource :projects do
- helpers do
- def handle_project_member_errors(errors)
- if errors[:project_access].any?
- error!(errors[:project_access], 422)
- end
- not_found!
- end
- end
-
# Get project hooks
#
# Parameters:
@@ -22,7 +13,7 @@ module API
# GET /projects/:id/hooks
get ":id/hooks" do
@hooks = paginate user_project.hooks
- present @hooks, with: Entities::Hook
+ present @hooks, with: Entities::ProjectHook
end
# Get a project hook
@@ -34,7 +25,7 @@ module API
# GET /projects/:id/hooks/:hook_id
get ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::Hook
+ present @hook, with: Entities::ProjectHook
end
@@ -47,10 +38,11 @@ module API
# POST /projects/:id/hooks
post ":id/hooks" do
required_attributes! [:url]
+ attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
+ @hook = user_project.hooks.new(attrs)
- @hook = user_project.hooks.new({"url" => params[:url]})
if @hook.save
- present @hook, with: Entities::Hook
+ present @hook, with: Entities::ProjectHook
else
if @hook.errors[:url].present?
error!("Invalid url given", 422)
@@ -70,10 +62,10 @@ module API
put ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id])
required_attributes! [:url]
+ attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
- attrs = attributes_for_keys [:url]
if @hook.update_attributes attrs
- present @hook, with: Entities::Hook
+ present @hook, with: Entities::ProjectHook
else
if @hook.errors[:url].present?
error!("Invalid url given", 422)
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
new file mode 100644
index 0000000000..47c4ddce16
--- /dev/null
+++ b/lib/api/project_members.rb
@@ -0,0 +1,114 @@
+module API
+ # Projects members API
+ class ProjectMembers < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project team members
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # query - Query string
+ # Example Request:
+ # GET /projects/:id/members
+ get ":id/members" do
+ 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 of a project
+ # user_id (required) - The ID of a user
+ # Example Request:
+ # GET /projects/:id/members/:user_id
+ get ":id/members/:user_id" do
+ @member = user_project.users.find params[:user_id]
+ present @member, with: Entities::ProjectMember, project: user_project
+ end
+
+ # Add a new project team member
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a user
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /projects/:id/members
+ post ":id/members" do
+ authorize! :admin_project, user_project
+ required_attributes! [:user_id, :access_level]
+
+ # either the user is already a team member or a new one
+ team_member = user_project.team_member_by_id(params[:user_id])
+ if team_member.nil?
+ team_member = user_project.users_projects.new(
+ user_id: params[:user_id],
+ project_access: params[:access_level]
+ )
+ end
+
+ if team_member.save
+ @member = team_member.user
+ present @member, with: Entities::ProjectMember, project: user_project
+ else
+ handle_project_member_errors team_member.errors
+ end
+ end
+
+ # Update project team member
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a team member
+ # access_level (required) - Project access level
+ # Example Request:
+ # PUT /projects/:id/members/:user_id
+ put ":id/members/:user_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:access_level]
+
+ team_member = user_project.users_projects.find_by(user_id: params[:user_id])
+ not_found!("User can not be found") if team_member.nil?
+
+ if team_member.update_attributes(project_access: params[:access_level])
+ @member = team_member.user
+ present @member, with: Entities::ProjectMember, project: user_project
+ else
+ handle_project_member_errors team_member.errors
+ end
+ end
+
+ # Remove a team member from project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # user_id (required) - The ID of a team member
+ # Example Request:
+ # DELETE /projects/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ authorize! :admin_project, user_project
+ team_member = user_project.users_projects.find_by(user_id: params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
+ else
+ {message: "Access revoked", id: params[:user_id].to_i}
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index bee6544ea3..8e09fff684 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -41,7 +41,6 @@ module API
# 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
# code (required) - The content of a snippet
# Example Request:
# POST /projects/:id/snippets
@@ -50,7 +49,6 @@ module API
required_attributes! [:title, :file_name, :code]
attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
attrs[:content] = params[:code] if params[:code].present?
@snippet = user_project.snippets.new attrs
@snippet.author = current_user
@@ -69,7 +67,6 @@ module API
# 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
- # lifetime (optional) - The expiration date of a snippet
# code (optional) - The content of a snippet
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
@@ -78,7 +75,6 @@ module API
authorize! :modify_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
attrs[:content] = params[:code] if params[:code].present?
if @snippet.update_attributes attrs
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 221f1f1e23..9d290c75ba 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -5,11 +5,11 @@ module API
resource :projects do
helpers do
- def handle_project_member_errors(errors)
- if errors[:project_access].any?
- error!(errors[:project_access], 422)
- end
- not_found!
+ def map_public_to_visibility_level(attrs)
+ publik = attrs.delete(:public)
+ publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik)
+ attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
+ attrs
end
end
@@ -31,6 +31,16 @@ module API
present @projects, with: Entities::Project
end
+ # Get all projects for admin user
+ #
+ # Example Request:
+ # GET /projects/all
+ get '/all' do
+ authenticated_as_admin!
+ @projects = paginate Project
+ present @projects, with: Entities::Project
+ end
+
# Get a single project
#
# Parameters:
@@ -38,7 +48,7 @@ module API
# Example Request:
# GET /projects/:id
get ":id" do
- present user_project, with: Entities::Project
+ present user_project, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project events
@@ -60,14 +70,14 @@ module API
# Parameters:
# name (required) - name for new project
# description (optional) - short project description
- # default_branch (optional) - 'master' by default
# issues_enabled (optional)
# wall_enabled (optional)
# merge_requests_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
# namespace_id (optional) - defaults to user namespace
- # public (optional) - false by default
+ # public (optional) - if true same as setting visibility_level = 20
+ # visibility_level (optional) - 0 by default
# Example Request
# POST /projects
post do
@@ -75,15 +85,17 @@ module API
attrs = attributes_for_keys [:name,
:path,
:description,
- :default_branch,
:issues_enabled,
:wall_enabled,
:merge_requests_enabled,
:wiki_enabled,
:snippets_enabled,
:namespace_id,
- :public]
- @project = ::Projects::CreateContext.new(current_user, attrs).execute
+ :public,
+ :visibility_level,
+ :import_url]
+ attrs = map_public_to_visibility_level(attrs)
+ @project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
else
@@ -106,7 +118,8 @@ module API
# merge_requests_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
- # public (optional)
+ # public (optional) - if true same as setting visibility_level = 20
+ # visibility_level (optional)
# Example Request
# POST /projects/user/:user_id
post "user/:user_id" do
@@ -120,8 +133,10 @@ module API
:merge_requests_enabled,
:wiki_enabled,
:snippets_enabled,
- :public]
- @project = ::Projects::CreateContext.new(user, attrs).execute
+ :public,
+ :visibility_level]
+ attrs = map_public_to_visibility_level(attrs)
+ @project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
else
@@ -174,104 +189,6 @@ module API
user_project.forked_project_link.destroy
end
end
-
- # Get a project team members
- #
- # Parameters:
- # id (required) - The ID of a project
- # query - Query string
- # Example Request:
- # GET /projects/:id/members
- get ":id/members" do
- 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 of a project
- # user_id (required) - The ID of a user
- # Example Request:
- # GET /projects/:id/members/:user_id
- get ":id/members/:user_id" do
- @member = user_project.users.find params[:user_id]
- present @member, with: Entities::ProjectMember, project: user_project
- end
-
- # Add a new project team member
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a user
- # access_level (required) - Project access level
- # Example Request:
- # POST /projects/:id/members
- post ":id/members" do
- authorize! :admin_project, user_project
- required_attributes! [:user_id, :access_level]
-
- # either the user is already a team member or a new one
- team_member = user_project.team_member_by_id(params[:user_id])
- if team_member.nil?
- team_member = user_project.users_projects.new(
- user_id: params[:user_id],
- project_access: params[:access_level]
- )
- end
-
- if team_member.save
- @member = team_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_project_member_errors team_member.errors
- end
- end
-
- # Update project team member
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a team member
- # access_level (required) - Project access level
- # Example Request:
- # PUT /projects/:id/members/:user_id
- put ":id/members/:user_id" do
- authorize! :admin_project, user_project
- required_attributes! [:access_level]
-
- team_member = user_project.users_projects.find_by_user_id(params[:user_id])
- not_found!("User can not be found") if team_member.nil?
-
- if team_member.update_attributes(project_access: params[:access_level])
- @member = team_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_project_member_errors team_member.errors
- end
- end
-
- # Remove a team member from project
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a team member
- # Example Request:
- # DELETE /projects/:id/members/:user_id
- delete ":id/members/:user_id" do
- authorize! :admin_project, user_project
- team_member = user_project.users_projects.find_by_user_id(params[:user_id])
- unless team_member.nil?
- team_member.destroy
- else
- {message: "Access revoked", id: params[:user_id].to_i}
- end
- end
-
# search for projects current_user has access to
#
# Parameters:
@@ -282,9 +199,33 @@ module API
# GET /projects/search/:query
get "/search/:query" do
ids = current_user.authorized_projects.map(&:id)
- projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%")
+ visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
+ projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
present paginate(projects), with: Entities::Project
end
+
+
+ # Get a users list
+ #
+ # Example Request:
+ # GET /users
+ get ':id/users' do
+ @users = User.where(id: user_project.team.users.map(&:id))
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
+ present @users, with: Entities::User
+ end
+
+ # Get a project labels
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/labels
+ get ':id/labels' do
+ @labels = user_project.issues_labels
+ present @labels, with: Entities::Label
+ end
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index c9422fdb16..076a9ceeb7 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -1,3 +1,5 @@
+require 'mime/types'
+
module API
# Projects API
class Repositories < Grape::API
@@ -13,66 +15,6 @@ module API
not_found!
end
end
-
- # Get a project repository branches
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/branches
- get ":id/repository/branches" do
- present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
- end
-
- # Get a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # GET /projects/:id/repository/branches/:branch
- get ":id/repository/branches/:branch" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Protect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/protect
- put ":id/repository/branches/:branch/protect" do
- authorize_admin_project
-
- @branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
- protected_branch = user_project.protected_branches.find_by_name(@branch.name)
- user_project.protected_branches.create(name: @branch.name) unless protected_branch
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Unprotect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/unprotect
- put ":id/repository/branches/:branch/unprotect" do
- authorize_admin_project
-
- @branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
- protected_branch = user_project.protected_branches.find_by_name(@branch.name)
- protected_branch.destroy if protected_branch
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
# Get a project repository tags
#
# Parameters:
@@ -80,51 +22,7 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
- end
-
- # Get a project repository commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
- # Example Request:
- # GET /projects/:id/repository/commits
- get ":id/repository/commits" do
- page = (params[:page] || 0).to_i
- per_page = (params[:per_page] || 20).to_i
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
-
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
- present commits, with: Entities::RepoCommit
- end
-
- # Get a specific commit of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit hash or name of a repository branch or tag
- # Example Request:
- # GET /projects/:id/repository/commits/:sha
- get ":id/repository/commits/:sha" do
- sha = params[:sha]
- commit = user_project.repository.commit(sha)
- not_found! "Commit" unless commit
- present commit, with: Entities::RepoCommit
- end
-
- # Get the diff for a specific commit of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit or branch name
- # Example Request:
- # GET /projects/:id/repository/commits/:sha/diff
- get ":id/repository/commits/:sha/diff" do
- sha = params[:sha]
- result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute
- not_found! "Commit" unless result[:commit]
- result[:commit].diffs
+ present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
end
# Get a project repository tree
@@ -139,15 +37,9 @@ module API
path = params[:path] || nil
commit = user_project.repository.commit(ref)
- tree = Tree.new(user_project.repository, commit.id, path)
+ tree = user_project.repository.tree(commit.id, path)
- trees = []
-
- %w(trees blobs submodules).each do |type|
- trees += tree.send(type).map { |t| {name: t.name, type: type.singularize, mode: t.mode, id: t.id} }
- end
-
- trees
+ present tree.sorted_entries, with: Entities::RepoTreeObject
end
# Get a raw file contents
@@ -171,6 +63,26 @@ module API
blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
not_found! "File" unless blob
+ content_type 'text/plain'
+ present blob.data
+ end
+
+ # Get a raw blob contents by blob sha
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The blob's sha
+ # Example Request:
+ # GET /projects/:id/repository/raw_blobs/:sha
+ get ":id/repository/raw_blobs/:sha" do
+ ref = params[:sha]
+
+ repo = user_project.repository
+
+ blob = Gitlab::Git::Blob.raw(repo, ref)
+
+ not_found! "Blob" unless blob
+
env['api.format'] = :txt
content_type blob.mime_type
@@ -184,18 +96,20 @@ module API
# sha (optional) - the commit sha to download defaults to the tip of the default branch
# Example Request:
# GET /projects/:id/repository/archive
- get ":id/repository/archive" do
+ get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do
authorize! :download_code, user_project
repo = user_project.repository
ref = params[:sha]
- storage_path = Rails.root.join("tmp", "repositories")
+ format = params[:format]
+ storage_path = Gitlab.config.gitlab.repository_downloads_path
- file_path = repo.archive_repo(ref, storage_path)
+ file_path = repo.archive_repo(ref, storage_path, format)
if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read
- header "Content-Disposition:", " infile; filename=\"#{File.basename(file_path)}\""
- content_type 'application/x-gzip'
+ header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\""
+
+ content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary
@@ -207,4 +121,3 @@ module API
end
end
end
-
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 54d3aeecb7..6ed2740c33 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -9,7 +9,7 @@ module API
# Example Request:
# GET /users
get do
- @users = User.scoped
+ @users = User.all
@users = @users.active if params[:active].present?
@users = @users.search(params[:search]) if params[:search].present?
@users = paginate @users
@@ -36,6 +36,7 @@ module API
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
+ # website_url - Website url
# projects_limit - Number of projects user can create
# extern_uid - External authentication provider UID
# provider - External provider
@@ -67,6 +68,7 @@ module API
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
+ # website_url - Website url
# projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
@@ -78,7 +80,7 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
user = User.find(params[:id])
not_found!("User not found") unless user
@@ -111,13 +113,52 @@ module API
end
end
+ # Get ssh keys of a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # uid (required) - The ID of a user
+ # Example Request:
+ # GET /users/:uid/keys
+ get ':uid/keys' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:uid])
+ if user
+ present user.keys, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
+ # Delete existing ssh key of a specified user. Only available to admin
+ # users.
+ #
+ # Parameters:
+ # uid (required) - The ID of a user
+ # id (required) - SSH Key ID
+ # Example Request:
+ # DELETE /users/:uid/keys/:id
+ delete ':uid/keys/:id' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:uid])
+ if user
+ begin
+ key = user.keys.find params[:id]
+ key.destroy
+ rescue ActiveRecord::RecordNotFound
+ not_found!
+ end
+ else
+ not_found!
+ end
+ end
+
# Delete user. Available only for admin
#
# Example Request:
# DELETE /users/:id
delete ":id" do
authenticated_as_admin!
- user = User.find_by_id(params[:id])
+ user = User.find_by(id: params[:id])
if user
user.destroy
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index c4fb2e2e15..7b6908ccad 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -11,23 +11,32 @@ module Backup
end
def dump
- case config["adapter"]
+ success = case config["adapter"]
when /^mysql/ then
- system("mysqldump #{mysql_args} #{config['database']} > #{db_file_name}")
+ print "Dumping MySQL database #{config['database']} ... "
+ system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then
+ print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
- system("pg_dump #{config['database']} > #{db_file_name}")
+ system('pg_dump', config['database'], out: db_file_name)
end
+ report_success(success)
end
def restore
- case config["adapter"]
+ success = case config["adapter"]
when /^mysql/ then
- system("mysql #{mysql_args} #{config['database']} < #{db_file_name}")
+ print "Restoring MySQL database #{config['database']} ... "
+ system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
+ print "Restoring PostgreSQL database #{config['database']} ... "
+ # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
+ # statements like MySQL.
+ Rake::Task["gitlab:db:drop_all_tables"].invoke
pg_env
- system("psql #{config['database']} -f #{db_file_name}")
+ system('psql', config['database'], '-f', db_file_name)
end
+ report_success(success)
end
protected
@@ -45,7 +54,7 @@ module Backup
'encoding' => '--default-character-set',
'password' => '--password'
}
- args.map { |opt, arg| "#{arg}='#{config[opt]}'" if config[opt] }.compact.join(' ')
+ args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
end
def pg_env
@@ -54,5 +63,13 @@ module Backup
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
end
+
+ def report_success(success)
+ if success
+ puts '[DONE]'.green
+ else
+ puts '[FAILED]'.red
+ end
+ end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 258a0fb258..28e323fe30 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,12 +1,14 @@
module Backup
class Manager
+ BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml}
+
def pack
# saving additional informations
s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
s[:backup_created_at] = Time.now
- s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"")
- s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"")
+ s[:gitlab_version] = Gitlab::VERSION
+ s[:tar_version] = tar_version
Dir.chdir(Gitlab.config.backup.path)
@@ -16,7 +18,7 @@ module Backup
# create archive
print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... "
- if Kernel.system("tar -cf #{s[:backup_created_at].to_i}_gitlab_backup.tar repositories/ db/ uploads/ backup_information.yml")
+ if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS)
puts "done".green
else
puts "failed".red
@@ -25,7 +27,7 @@ module Backup
def cleanup
print "Deleting tmp directories ... "
- if Kernel.system("rm -rf repositories/ db/ uploads/ backup_information.yml")
+ if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
puts "done".green
else
puts "failed".red
@@ -44,7 +46,7 @@ module Backup
file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
file_list.sort.each do |timestamp|
if Time.at(timestamp) < (Time.now - keep_time)
- if system("rm #{timestamp}_gitlab_backup.tar")
+ if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar))
removed += 1
end
end
@@ -75,7 +77,7 @@ module Backup
end
print "Unpacking backup ... "
- unless Kernel.system("tar -xf #{tar_file}")
+ unless Kernel.system(*%W(tar -xf #{tar_file}))
puts "failed".red
exit 1
else
@@ -85,22 +87,21 @@ module Backup
settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
- # backups directory is not always sub of Rails root and able to execute the git rev-parse below
- begin
- Dir.chdir(Rails.root)
-
- # restoring mismatching backups can lead to unexpected problems
- if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/, "")
- puts "GitLab version mismatch:".red
- puts " Your current HEAD differs from the HEAD in the backup!".red
- puts " Please switch to the following revision and try again:".red
- puts " revision: #{settings[:gitlab_version]}".red
- exit 1
- end
- ensure
- # chdir back to original intended dir
- Dir.chdir(Gitlab.config.backup.path)
+ # restoring mismatching backups can lead to unexpected problems
+ if settings[:gitlab_version] != Gitlab::VERSION
+ puts "GitLab version mismatch:".red
+ puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".red
+ puts " Please switch to the following version and try again:".red
+ puts " version: #{settings[:gitlab_version]}".red
+ puts
+ puts "Hint: git checkout v#{settings[:gitlab_version]}"
+ exit 1
end
end
+
+ def tar_version
+ tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
+ tar_version.force_encoding('locale').split("\n").first
+ end
end
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 252201f11b..6f7c4f7c90 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -10,27 +10,24 @@ module Backup
Project.find_each(batch_size: 1000) do |project|
print " * #{project.path_with_namespace} ... "
- if project.empty_repo?
- puts "[SKIPPED]".cyan
- next
- end
-
# Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
- if system("cd #{path_to_repo(project)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(project)} --all > /dev/null 2>&1")
+ if project.empty_repo?
+ puts "[SKIPPED]".cyan
+ elsif system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent)
puts "[DONE]".green
else
puts "[FAILED]".red
end
- wiki = GollumWiki.new(project)
+ wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
print " * #{wiki.path_with_namespace} ... "
if wiki.empty?
puts " [SKIPPED]".cyan
- elsif system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1")
+ elsif system(*%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all), silent)
puts " [DONE]".green
else
puts " [FAILED]".red
@@ -53,17 +50,17 @@ module Backup
project.namespace.ensure_dir_exist if project.namespace
- if system("git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)} > /dev/null 2>&1")
+ if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent)
puts "[DONE]".green
else
puts "[FAILED]".red
end
- wiki = GollumWiki.new(project)
+ wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki))
print " * #{wiki.path_with_namespace} ... "
- if system("git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)} > /dev/null 2>&1")
+ if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
puts " [DONE]".green
else
puts " [FAILED]".red
@@ -72,8 +69,7 @@ module Backup
end
print 'Put GitLab hooks in repositories dirs'.yellow
- gitlab_shell_user_home = File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
- if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh #{Gitlab.config.gitlab_shell.repos_path}")
+ if system("#{Gitlab.config.gitlab_shell.path}/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path)
puts " [DONE]".green
else
puts " [FAILED]".red
@@ -103,5 +99,9 @@ module Backup
FileUtils.rm_rf(backup_repos_path)
FileUtils.mkdir_p(backup_repos_path)
end
+
+ def silent
+ {err: '/dev/null', out: '/dev/null'}
+ end
end
end
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 462d3f1e27..e50e1ff4f1 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -3,7 +3,7 @@ module Backup
attr_reader :app_uploads_dir, :backup_uploads_dir, :backup_dir
def initialize
- @app_uploads_dir = Rails.root.join('public', 'uploads')
+ @app_uploads_dir = File.realpath(Rails.root.join('public', 'uploads'))
@backup_dir = Gitlab.config.backup.path
@backup_uploads_dir = File.join(Gitlab.config.backup.path, 'uploads')
end
@@ -19,10 +19,11 @@ module Backup
FileUtils.cp_r(backup_uploads_dir, app_uploads_dir)
end
-
+
def backup_existing_uploads_dir
+ timestamped_uploads_path = File.join(app_uploads_dir, '..', "uploads.#{Time.now.to_i}")
if File.exists?(app_uploads_dir)
- FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}"))
+ FileUtils.mv(app_uploads_dir, timestamped_uploads_path)
end
end
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 6e7872dcd0..e51cb30bdd 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -117,7 +117,7 @@ module ExtractsPath
end
def tree
- @tree ||= Tree.new(@repo, @commit.id, @path)
+ @tree ||= @repo.tree(@commit.id, @path)
end
private
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 0f19629747..955abc1bed 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,7 +1,7 @@
module Gitlab
class Auth
def find(login, password)
- user = User.find_by_email(login) || User.find_by_username(login)
+ user = User.find_by(email: login) || User.find_by(username: login)
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index e09cf31197..c2f3b851c0 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,11 +1,9 @@
require_relative 'shell_env'
-require_relative 'grack_helpers'
module Grack
class Auth < Rack::Auth::Basic
- include Helpers
- attr_accessor :user, :project, :ref, :env
+ attr_accessor :user, :project, :env
def call(env)
@env = env
@@ -24,14 +22,16 @@ module Grack
@env['SCRIPT_NAME'] = ""
- auth!
+ if project
+ auth!
+ else
+ render_not_found
+ end
end
private
def auth!
- return render_not_found unless project
-
if @auth.provided?
return bad_request unless @auth.basic?
@@ -40,12 +40,8 @@ module Grack
# Allow authentication for GitLab CI service
# if valid token passed
- if login == "gitlab-ci-token" && project.gitlab_ci?
- token = project.gitlab_ci_service.token
-
- if token.present? && token == password && service_name == 'git-upload-pack'
- return @app.call(env)
- end
+ if gitlab_ci_request?(login, password)
+ return @app.call(env)
end
@user = authenticate_user(login, password)
@@ -53,23 +49,26 @@ module Grack
if @user
Gitlab::ShellEnv.set_env(@user)
@env['REMOTE_USER'] = @auth.username
- else
- return unauthorized
end
-
- else
- return unauthorized unless project.public
end
- if authorized_git_request?
+ if authorized_request?
@app.call(env)
else
unauthorized
end
end
- def authorized_git_request?
- authorize_request(service_name)
+ def gitlab_ci_request?(login, password)
+ if login == "gitlab-ci-token" && project.gitlab_ci?
+ token = project.gitlab_ci_service.token
+
+ if token.present? && token == password && git_cmd == 'git-upload-pack'
+ return true
+ end
+ end
+
+ false
end
def authenticate_user(login, password)
@@ -77,24 +76,31 @@ module Grack
auth.find(login, password)
end
- def authorize_request(service)
- case service
- when 'git-upload-pack'
- project.public || can?(user, :download_code, project)
- when'git-receive-pack'
- action = if project.protected_branch?(ref)
- :push_code_to_protected_branches
- else
- :push_code
- end
-
- can?(user, action, project)
+ def authorized_request?
+ case git_cmd
+ when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
+ if user
+ Gitlab::GitAccess.new.download_allowed?(user, project)
+ elsif project.public?
+ # Allow clone/fetch for public projects
+ true
+ else
+ false
+ end
+ when *Gitlab::GitAccess::PUSH_COMMANDS
+ if user
+ # Skip user authorization on upload request.
+ # It will be serverd by update hook in repository
+ true
+ else
+ false
+ end
else
false
end
end
- def service_name
+ def git_cmd
if @request.get?
@request.params['service']
elsif @request.post?
@@ -108,20 +114,17 @@ module Grack
@project ||= project_by_path(@request.path_info)
end
- def ref
- @ref ||= parse_ref
+ def project_by_path(path)
+ if m = /^([\w\.\/-]+)\.git/.match(path).to_a
+ path_with_namespace = m.last
+ path_with_namespace.gsub!(/\.wiki$/, '')
+
+ Project.find_with_namespace(path_with_namespace)
+ end
end
- def parse_ref
- input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
- Zlib::GzipReader.new(@request.body).read
- else
- @request.body.read
- end
-
- # Need to reset seek point
- @request.body.rewind
- /refs\/heads\/([\/\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last
+ def render_not_found
+ [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
end
end
end
diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb
deleted file mode 100644
index 5ac9e9f325..0000000000
--- a/lib/gitlab/backend/grack_helpers.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Grack
- module Helpers
- def project_by_path(path)
- if m = /^\/([\w\.\/-]+)\.git/.match(path).to_a
- path_with_namespace = m.last
- path_with_namespace.gsub!(/\.wiki$/, '')
-
- Project.find_with_namespace(path_with_namespace)
- end
- end
-
- def render_not_found
- [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
- end
-
- def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
- end
-
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
- end
- end
-end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index c819ce56ac..b93800e235 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -2,6 +2,12 @@ module Gitlab
class Shell
class AccessDenied < StandardError; end
+ class KeyAdder < Struct.new(:io)
+ def add_key(id, key)
+ io.puts("#{id}\t#{key.strip}")
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -10,7 +16,7 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "add-project", "#{name}.git"
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git"
end
# Import repository
@@ -21,7 +27,7 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "import-project", "#{name}.git", url
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url
end
# Move repository
@@ -33,7 +39,7 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
#
def mv_repository(path, new_path)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
end
# Update HEAD for repository
@@ -45,7 +51,7 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch
end
# Fork repository to new namespace
@@ -57,7 +63,7 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
end
# Remove repository from file system
@@ -68,7 +74,7 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-project", "#{name}.git"
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git"
end
# Add repository branch from passed ref
@@ -81,7 +87,7 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
end
# Remove repository branch
@@ -93,7 +99,7 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
end
# Add repository tag from passed ref
@@ -106,7 +112,7 @@ module Gitlab
# add_tag("gitlab/gitlab-ci", "v4.0", "master")
#
def add_tag(path, tag_name, ref)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
end
# Remove repository tag
@@ -118,7 +124,7 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
+ system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
end
# Add new key to gitlab-shell
@@ -127,7 +133,17 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "add-key", key_id, key_content
+ system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
+ end
+
+ # Batch-add keys to authorized_keys
+ #
+ # Ex.
+ # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
+ def batch_add_keys(&block)
+ IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
+ block.call(KeyAdder.new(io))
+ end
end
# Remove ssh key from gitlab shell
@@ -136,7 +152,7 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "rm-key", key_id, key_content
+ system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content
end
# Remove all ssh keys from gitlab shell
@@ -145,7 +161,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "clear"
+ system "#{gitlab_shell_path}/bin/gitlab-keys", "clear"
end
# Add empty directory for storing repositories
@@ -196,8 +212,21 @@ module Gitlab
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
+ # Return GitLab shell version
+ def version
+ gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION"
+
+ if File.readable?(gitlab_shell_version_file)
+ File.read(gitlab_shell_version_file)
+ end
+ end
+
protected
+ def gitlab_shell_path
+ Gitlab.config.gitlab_shell.path
+ end
+
def gitlab_shell_user_home
File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
end
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
index 2f9091e07d..6bc2c3b487 100644
--- a/lib/gitlab/blacklist.rb
+++ b/lib/gitlab/blacklist.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
def path
- %w(admin dashboard groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes)
+ %w(admin dashboard files groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes)
end
end
end
diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb
index fb27280c4a..14bbb32863 100644
--- a/lib/gitlab/diff_parser.rb
+++ b/lib/gitlab/diff_parser.rb
@@ -4,9 +4,9 @@ module Gitlab
attr_reader :lines, :new_path
- def initialize(diff)
- @lines = diff.diff.lines.to_a
- @new_path = diff.new_path
+ def initialize(lines, new_path = '')
+ @lines = lines
+ @new_path = new_path
end
def each
@@ -18,10 +18,7 @@ module Gitlab
lines_arr.each do |line|
raw_line = line.dup
- next if line.match(/^\-\-\- \/dev\/null/)
- next if line.match(/^\+\+\+ \/dev\/null/)
- next if line.match(/^\-\-\- a/)
- next if line.match(/^\+\+\+ b/)
+ next if filename?(line)
full_line = html_escape(line.gsub(/\n/, ''))
full_line = ::Gitlab::InlineDiff.replace_markers full_line
@@ -53,8 +50,17 @@ module Gitlab
end
end
+ def empty?
+ @lines.empty?
+ end
+
private
+ def filename?(line)
+ line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
+ '--- /tmp/diffy', '+++ /tmp/diffy')
+ end
+
def identification_type(line)
if line[0] == "+"
"new"
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
new file mode 100644
index 0000000000..4f49ca4189
--- /dev/null
+++ b/lib/gitlab/git_access.rb
@@ -0,0 +1,67 @@
+module Gitlab
+ class GitAccess
+ DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
+ PUSH_COMMANDS = %w{ git-receive-pack }
+
+ attr_reader :params, :project, :git_cmd, :user
+
+ def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false)
+ case cmd
+ when *DOWNLOAD_COMMANDS
+ if actor.is_a? User
+ download_allowed?(actor, project)
+ elsif actor.is_a? DeployKey
+ actor.projects.include?(project)
+ elsif actor.is_a? Key
+ download_allowed?(actor.user, project)
+ else
+ raise 'Wrong actor'
+ end
+ when *PUSH_COMMANDS
+ if actor.is_a? User
+ push_allowed?(actor, project, ref, oldrev, newrev, forced_push)
+ elsif actor.is_a? DeployKey
+ # Deploy key not allowed to push
+ return false
+ elsif actor.is_a? Key
+ push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push)
+ else
+ raise 'Wrong actor'
+ end
+ else
+ false
+ end
+ end
+
+ def download_allowed?(user, project)
+ if user && user_allowed?(user)
+ user.can?(:download_code, project)
+ else
+ false
+ end
+ end
+
+ def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
+ if user && user_allowed?(user)
+ action = if project.protected_branch?(ref)
+ if forced_push.to_s == 'true'
+ :force_push_code_to_protected_branches
+ else
+ :push_code_to_protected_branches
+ end
+ else
+ :push_code
+ end
+ user.can?(action, project)
+ else
+ false
+ end
+ end
+
+ private
+
+ def user_allowed?(user)
+ Gitlab::UserAccess.allowed?(user)
+ end
+ end
+end
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
index a1ff248a77..6e4de197ee 100644
--- a/lib/gitlab/identifier.rb
+++ b/lib/gitlab/identifier.rb
@@ -6,17 +6,17 @@ module Gitlab
if identifier.blank?
# Local push from gitlab
email = project.repository.commit(newrev).author_email rescue nil
- User.find_by_email(email) if email
+ User.find_by(email: email) if email
elsif identifier =~ /\Auser-\d+\Z/
# git push over http
user_id = identifier.gsub("user-", "")
- User.find_by_id(user_id)
+ User.find_by(id: user_id)
elsif identifier =~ /\Akey-\d+\Z/
# git push over ssh
key_id = identifier.gsub("key-", "")
- Key.find_by_id(key_id).try(:user)
+ Key.find_by(id: key_id).try(:user)
end
end
end
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
new file mode 100644
index 0000000000..4e48ff1187
--- /dev/null
+++ b/lib/gitlab/ldap/access.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module LDAP
+ class Access
+ attr_reader :adapter
+
+ def self.open(&block)
+ Gitlab::LDAP::Adapter.open do |adapter|
+ block.call(self.new(adapter))
+ end
+ end
+
+ def initialize(adapter=nil)
+ @adapter = adapter
+ end
+
+ def allowed?(user)
+ if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
+ !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter)
+ else
+ false
+ end
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
new file mode 100644
index 0000000000..e36616f0e6
--- /dev/null
+++ b/lib/gitlab/ldap/adapter.rb
@@ -0,0 +1,107 @@
+module Gitlab
+ module LDAP
+ class Adapter
+ attr_reader :ldap
+
+ def self.open(&block)
+ Net::LDAP.open(adapter_options) do |ldap|
+ block.call(self.new(ldap))
+ end
+ end
+
+ def self.config
+ Gitlab.config.ldap
+ end
+
+ def self.adapter_options
+ encryption = config['method'].to_s == 'ssl' ? :simple_tls : nil
+
+ options = {
+ host: config['host'],
+ port: config['port'],
+ encryption: encryption
+ }
+
+ auth_options = {
+ auth: {
+ method: :simple,
+ username: config['bind_dn'],
+ password: config['password']
+ }
+ }
+
+ if config['password'] || config['bind_dn']
+ options.merge!(auth_options)
+ end
+ options
+ end
+
+
+ def initialize(ldap=nil)
+ @ldap = ldap || Net::LDAP.new(self.class.adapter_options)
+ end
+
+ def users(field, value)
+ if field.to_sym == :dn
+ options = {
+ base: value,
+ scope: Net::LDAP::SearchScope_BaseObject
+ }
+ else
+ options = {
+ base: config['base'],
+ filter: Net::LDAP::Filter.eq(field, value)
+ }
+ end
+
+ if config['user_filter'].present?
+ user_filter = Net::LDAP::Filter.construct(config['user_filter'])
+
+ options[:filter] = if options[:filter]
+ Net::LDAP::Filter.join(options[:filter], user_filter)
+ else
+ user_filter
+ end
+ end
+
+ entries = ldap_search(options).select do |entry|
+ entry.respond_to? config.uid
+ end
+
+ entries.map do |entry|
+ Gitlab::LDAP::Person.new(entry)
+ end
+ end
+
+ def user(*args)
+ users(*args).first
+ end
+
+ def dn_matches_filter?(dn, filter)
+ ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any?
+ end
+
+ def ldap_search(*args)
+ results = ldap.search(*args)
+
+ if results.nil?
+ response = ldap.get_operation_result
+
+ unless response.code.zero?
+ Rails.logger.warn("LDAP search error: #{response.message}")
+ end
+
+ []
+ else
+ results
+ end
+ end
+
+ private
+
+ def config
+ @config ||= self.class.config
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
new file mode 100644
index 0000000000..9ad6618bd4
--- /dev/null
+++ b/lib/gitlab/ldap/person.rb
@@ -0,0 +1,60 @@
+module Gitlab
+ module LDAP
+ class Person
+ # Active Directory-specific LDAP filter that checks if bit 2 of the
+ # userAccountControl attribute is set.
+ # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
+ AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
+
+ def self.find_by_uid(uid, adapter=nil)
+ adapter ||= Gitlab::LDAP::Adapter.new
+ adapter.user(config.uid, uid)
+ end
+
+ def self.find_by_dn(dn, adapter=nil)
+ adapter ||= Gitlab::LDAP::Adapter.new
+ adapter.user('dn', dn)
+ end
+
+ def self.active_directory_disabled?(dn, adapter=nil)
+ adapter ||= Gitlab::LDAP::Adapter.new
+ adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
+ end
+
+ def initialize(entry)
+ Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
+ @entry = entry
+ end
+
+ def name
+ entry.cn.first
+ end
+
+ def uid
+ entry.send(config.uid).first
+ end
+
+ def username
+ uid
+ end
+
+ def dn
+ entry.dn
+ end
+
+ private
+
+ def entry
+ @entry
+ end
+
+ def adapter
+ @adapter ||= Gitlab::LDAP::Adapter.new
+ end
+
+ def config
+ @config ||= Gitlab.config.ldap
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 3d57f3a2e3..01d86430f0 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -13,8 +13,8 @@ module Gitlab
def find_or_create(auth)
@auth = auth
- if uid.blank? || email.blank?
- raise_error("Account must provide an uid and email address")
+ if uid.blank? || email.blank? || username.blank?
+ raise_error("Account must provide a dn, uid and email address")
end
user = find(auth)
@@ -23,8 +23,8 @@ module Gitlab
# Look for user with same emails
#
# Possible cases:
- # * When user already has account and need to link his LDAP account.
- # * LDAP uid changed for user with same email and we need to update his uid
+ # * When user already has account and need to link their LDAP account.
+ # * LDAP uid changed for user with same email and we need to update their uid
#
user = find_user(email)
@@ -44,13 +44,13 @@ module Gitlab
end
def find_user(email)
- user = model.find_by_email(email)
+ user = model.find_by(email: email)
# If no user found and allow_username_or_email_login is true
- # we look for user by extracting part of his email
+ # we look for user by extracting part of their email
if !user && email && ldap_conf['allow_username_or_email_login']
uname = email.partition('@').first
- user = model.find_by_username(uname)
+ user = model.find_by(username: uname)
end
user
@@ -62,8 +62,16 @@ module Gitlab
return nil unless ldap_conf.enabled && login.present? && password.present?
ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ filter = Net::LDAP::Filter.eq(ldap.uid, login)
+
+ # Apply LDAP user filter if present
+ if ldap_conf['user_filter'].present?
+ user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
+ filter = Net::LDAP::Filter.join(filter, user_filter)
+ end
+
ldap_user = ldap.bind_as(
- filter: Net::LDAP::Filter.eq(ldap.uid, login),
+ filter: filter,
size: 1,
password: password
)
@@ -71,20 +79,19 @@ module Gitlab
find_by_uid(ldap_user.dn) if ldap_user
end
- # Check LDAP user existance by dn. User in git over ssh check
- #
- # It covers 2 cases:
- # * when ldap account was removed
- # * when ldap account was deactivated by change of OU membership in 'dn'
- def blocked?(dn)
- ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
- ldap.connection.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject, size: 1).blank?
- end
-
private
+ def find_by_uid_and_provider
+ find_by_uid(uid)
+ end
+
def find_by_uid(uid)
- model.where(provider: provider, extern_uid: uid).last
+ # LDAP distinguished name is case-insensitive
+ model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
+ end
+
+ def username
+ auth.info.nickname.to_s.force_encoding("utf-8")
end
def provider
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 389eef3395..64cf3303ea 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -11,12 +11,14 @@ module Gitlab
def self.read_latest
path = Rails.root.join("log", file_name)
self.build unless File.exist?(path)
- logs = `tail -n 2000 #{path}`.split("\n")
+ tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
+ tail_output.split("\n")
end
def self.read_latest_for filename
path = Rails.root.join("log", filename)
- logs = `tail -n 2000 #{path}`.split("\n")
+ tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path}))
+ tail_output.split("\n")
end
def self.build
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index dc9c0e0ab2..dca3d7a7be 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -98,7 +98,8 @@ module Gitlab
(?\W)? # Prefix
( # Reference
@(?[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
- |\#(?([a-zA-Z]+-)?\d+) # Issue ID
+ |(?([A-Z\-]+-)\d+) # JIRA Issue ID
+ |\#(?([a-zA-Z\-]+-)?\d+) # Issue ID
|!(?\d+) # MR ID
|\$(?\d+) # Snippet ID
|(?[\h]{6,40}) # Commit ID
@@ -152,7 +153,7 @@ module Gitlab
#
# Returns boolean
def valid_emoji?(emoji)
- Emoji.names.include? emoji
+ Emoji.find_by_name emoji
end
# Private: Dispatches to a dedicated processing method based on reference
@@ -166,17 +167,21 @@ module Gitlab
end
def reference_user(identifier)
- if member = @project.team_members.find { |user| user.username == identifier }
- link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ if user = User.find_by_username(identifier)
+ link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}"))
end
end
def reference_issue(identifier)
- if @project.issue_exists? identifier
- url = url_for_issue(identifier)
- title = title_for_issue(identifier)
+ if @project.used_default_issues_tracker? || !external_issues_tracker_enabled?
+ if @project.issue_exists? identifier
+ url = url_for_issue(identifier)
+ title = title_for_issue(identifier)
- link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ end
+ else
+ reference_jira_issue(identifier) if @project.issues_tracker == "jira"
end
end
@@ -197,5 +202,12 @@ module Gitlab
link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}"))
end
end
+
+ def reference_jira_issue(identifier)
+ url = url_for_issue(identifier)
+ title = Gitlab.config.issues_tracker[@project.issues_tracker]["title"]
+
+ link_to("#{identifier}", url, html_options.merge(title: "Issue in #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ end
end
end
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
index ea9badba2c..d154bd8600 100644
--- a/lib/gitlab/oauth/user.rb
+++ b/lib/gitlab/oauth/user.rb
@@ -28,8 +28,21 @@ module Gitlab
}
user = model.build_user(opts, as: :admin)
+ user.skip_confirmation!
+
+ # Services like twitter and github does not return email via oauth
+ # In this case we generate temporary email and force user to fill it later
+ if user.email.blank?
+ user.generate_tmp_oauth_email
+ elsif provider != "ldap"
+ # Google oauth returns email but dont return nickname
+ # So we use part of email as username for new user
+ # For LDAP, username is already set to the user's
+ # uid/userid/sAMAccountName.
+ user.username = email.match(/^[^@]*/)[0]
+ end
+
user.save!
- user.confirm!
log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}"
if Gitlab.config.omniauth['block_auto_created_users'] && !ldap?
@@ -54,11 +67,15 @@ module Gitlab
end
def name
- auth.info.name.to_s.force_encoding("utf-8")
+ if auth.info.name.nil?
+ "#{auth.info.first_name} #{auth.info.last_name}".force_encoding('utf-8')
+ else
+ auth.info.name.to_s.force_encoding('utf-8')
+ end
end
def username
- email.match(/^[^@]*/)[0]
+ auth.info.nickname.to_s.force_encoding("utf-8")
end
def provider
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index 2f30fde207..e2fbafb389 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -1,15 +1,29 @@
+require 'fileutils'
+require 'open3'
+
module Gitlab
module Popen
- def popen(cmd, path)
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
vars = { "PWD" => path }
options = { chdir: path }
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
@cmd_output = ""
@cmd_status = 0
- Open3.popen3(vars, cmd, options) do |stdin, stdout, stderr, wait_thr|
- @cmd_status = wait_thr.value.exitstatus
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
@cmd_output << stdout.read
@cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
end
return @cmd_output, @cmd_status
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 55aa240a9f..e932b64f4f 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -7,7 +7,7 @@ module Gitlab
end
def project_name_regex
- /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/
end
def name_regex
@@ -18,33 +18,38 @@ module Gitlab
default_regex
end
+ def archive_formats_regex
+ #|zip|tar| tar.gz | tar.bz2 |
+ /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
+ end
+
def git_reference_regex
# Valid git ref regex, see:
# https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
%r{
(?!
- # doesn't begins with
- \/| # (rule #6)
- # doesn't contain
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
.*(?:
- [\/.]\.| # (rule #1,3)
- \/\/| # (rule #6)
- @\{| # (rule #8)
- \\ # (rule #9)
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
)
)
- [^\000-\040\177~^:?*\[]+ # (rule #4-5)
- # doesn't end with
- (? ex
+ handle_exception(ex)
+ end
+
+ # Retrieve an array of commits between the source and the target
+ def commits
+ in_locked_and_timed_satellite do |target_repo|
+ prepare_satellite!(target_repo)
+ update_satellite_source_and_target!(target_repo)
+ commits = target_repo.commits_between("origin/#{@target_branch}", "source/#{@source_branch}")
+ commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
+ commits
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ private
+
+ # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for diffs
+ def update_satellite_source_and_target!(target_repo)
+ target_repo.remote_add('source', @source_project.repository.path_to_repo)
+ target_repo.remote_fetch('source')
+ target_repo.git.checkout(default_options({b: true}), @target_branch, "origin/#{@target_branch}")
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb
new file mode 100644
index 0000000000..30462999aa
--- /dev/null
+++ b/lib/gitlab/satellite/files/delete_file_action.rb
@@ -0,0 +1,50 @@
+require_relative 'file_action'
+
+module Gitlab
+ module Satellite
+ class DeleteFileAction < FileAction
+ # Deletes file and creates a new commit for it
+ #
+ # Returns false if committing the change fails
+ # Returns false if pushing from the satellite to bare repo failed or was rejected
+ # Returns true otherwise
+ def commit!(content, commit_message)
+ in_locked_and_timed_satellite do |repo|
+ prepare_satellite!(repo)
+
+ # create target branch in satellite at the corresponding commit from bare repo
+ repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+
+ # update the file in the satellite's working dir
+ file_path_in_satellite = File.join(repo.working_dir, file_path)
+
+ # Prevent relative links
+ unless safe_path?(file_path_in_satellite)
+ Gitlab::GitLogger.error("FileAction: Relative path not allowed")
+ return false
+ end
+
+ File.delete(file_path_in_satellite)
+
+ # add removed file
+ repo.remove(file_path_in_satellite)
+
+ # commit the changes
+ # will raise CommandFailed when commit fails
+ repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+
+
+ # push commit back to bare repo
+ # will raise CommandFailed when push fails
+ repo.git.push({raise: true, timeout: true}, :origin, ref)
+
+ # everything worked
+ true
+ end
+ rescue Grit::Git::CommandFailed => ex
+ Gitlab::GitLogger.error(ex.message)
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
similarity index 59%
rename from lib/gitlab/satellite/edit_file_action.rb
rename to lib/gitlab/satellite/files/edit_file_action.rb
index d793d0ba8d..cbdf70f7d1 100644
--- a/lib/gitlab/satellite/edit_file_action.rb
+++ b/lib/gitlab/satellite/files/edit_file_action.rb
@@ -1,40 +1,40 @@
+require_relative 'file_action'
+
module Gitlab
module Satellite
# GitLab server-side file update and commit
- class EditFileAction < Action
- attr_accessor :file_path, :ref
-
- def initialize(user, project, ref, file_path)
- super user, project, git_timeout: 10.seconds
- @file_path = file_path
- @ref = ref
- end
-
+ class EditFileAction < FileAction
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
# Returns false if committing the change fails
- # Returns false if pushing from the satellite to Gitolite failed or was rejected
+ # Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
- def commit!(content, commit_message, last_commit)
- return false unless can_edit?(last_commit)
-
+ def commit!(content, commit_message, encoding)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
- # create target branch in satellite at the corresponding commit from Gitolite
+ # create target branch in satellite at the corresponding commit from bare repo
repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
# update the file in the satellite's working dir
file_path_in_satellite = File.join(repo.working_dir, file_path)
- File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
+
+ # Prevent relative links
+ unless safe_path?(file_path_in_satellite)
+ Gitlab::GitLogger.error("FileAction: Relative path not allowed")
+ return false
+ end
+
+ # Write file
+ write_file(file_path_in_satellite, content, encoding)
# commit the changes
# will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
- # push commit back to Gitolite
+ # push commit back to bare repo
# will raise CommandFailed when push fails
repo.git.push({raise: true, timeout: true}, :origin, ref)
@@ -45,13 +45,6 @@ module Gitlab
Gitlab::GitLogger.error(ex.message)
false
end
-
- protected
-
- def can_edit?(last_commit)
- current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha
- last_commit == current_last_commit
- end
end
end
end
diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb
new file mode 100644
index 0000000000..7701a6d5d6
--- /dev/null
+++ b/lib/gitlab/satellite/files/file_action.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Satellite
+ class FileAction < Action
+ attr_accessor :file_path, :ref
+
+ def initialize(user, project, ref, file_path)
+ super user, project, git_timeout: 10.seconds
+ @file_path = file_path
+ @ref = ref
+ end
+
+ def safe_path?(path)
+ File.absolute_path(path) == path
+ end
+
+ def write_file(abs_file_path, content, file_encoding = 'text')
+ if file_encoding == 'base64'
+ File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) }
+ else
+ File.open(abs_file_path, 'w') { |f| f.write(content) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb
new file mode 100644
index 0000000000..15e9b7a6f7
--- /dev/null
+++ b/lib/gitlab/satellite/files/new_file_action.rb
@@ -0,0 +1,55 @@
+require_relative 'file_action'
+
+module Gitlab
+ module Satellite
+ class NewFileAction < FileAction
+ # Updates the files content and creates a new commit for it
+ #
+ # Returns false if the ref has been updated while editing the file
+ # Returns false if committing the change fails
+ # Returns false if pushing from the satellite to bare repo failed or was rejected
+ # Returns true otherwise
+ def commit!(content, commit_message, encoding)
+ in_locked_and_timed_satellite do |repo|
+ prepare_satellite!(repo)
+
+ # create target branch in satellite at the corresponding commit from bare repo
+ repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+
+ file_path_in_satellite = File.join(repo.working_dir, file_path)
+ dir_name_in_satellite = File.dirname(file_path_in_satellite)
+
+ # Prevent relative links
+ unless safe_path?(file_path_in_satellite)
+ Gitlab::GitLogger.error("FileAction: Relative path not allowed")
+ return false
+ end
+
+ # Create dir if not exists
+ FileUtils.mkdir_p(dir_name_in_satellite)
+
+ # Write file
+ write_file(file_path_in_satellite, content, encoding)
+
+ # add new file
+ repo.add(file_path_in_satellite)
+
+ # commit the changes
+ # will raise CommandFailed when commit fails
+ repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+
+
+ # push commit back to bare repo
+ # will raise CommandFailed when push fails
+ repo.git.push({raise: true, timeout: true}, :origin, ref)
+
+ # everything worked
+ true
+ end
+ rescue Grit::Git::CommandFailed => ex
+ Gitlab::GitLogger.error(ex.message)
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
index d74d4194ff..5f17aa60b8 100644
--- a/lib/gitlab/satellite/merge_action.rb
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -24,11 +24,11 @@ module Gitlab
# Returns false if the merge produced conflicts
# Returns false if pushing from the satellite to the repository failed or was rejected
# Returns true otherwise
- def merge!
+ def merge!(merge_commit_message = nil)
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
- if merge_in_satellite!(merge_repo)
- # push merge back to Gitolite
+ if merge_in_satellite!(merge_repo, merge_commit_message)
+ # push merge back to bare repo
# will raise CommandFailed when push fails
merge_repo.git.push(default_options, :origin, merge_request.target_branch)
# remove source branch
@@ -49,14 +49,7 @@ module Gitlab
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
-
- if merge_request.for_fork?
- diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
- else
- diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
- end
-
- return diff
+ diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
@@ -88,14 +81,7 @@ module Gitlab
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
-
- if (merge_request.for_fork?)
- patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
- else
- patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}..#{merge_request.source_branch}")
- end
-
- return patch
+ patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
@@ -125,34 +111,26 @@ module Gitlab
#
# Returns false if the merge produced conflicts
# Returns true otherwise
- def merge_in_satellite!(repo)
+ def merge_in_satellite!(repo, message = nil)
update_satellite_source_and_target!(repo)
+ message ||= "Merge branch '#{merge_request.source_branch}' into '#{merge_request.target_branch}'"
+
# merge the source branch into the satellite
# will raise CommandFailed when merge fails
- if merge_request.for_fork?
- repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
- else
- repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
- end
+ repo.git.merge(default_options({no_ff: true}), "-m#{message}", "source/#{merge_request.source_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
# Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
def update_satellite_source_and_target!(repo)
- if merge_request.for_fork?
- repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
- repo.remote_fetch('source')
- repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
- else
- repo.git.checkout(default_options, "#{merge_request.source_branch}")
- repo.git.checkout(default_options({t: true}), "origin/#{merge_request.target_branch}")
- end
+ repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
+ repo.remote_fetch('source')
+ repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
-
end
end
end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index 6cb7814fae..05123ad9c4 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -1,6 +1,4 @@
module Gitlab
- class SatelliteNotExistError < StandardError; end
-
module Satellite
class Satellite
include Gitlab::Popen
@@ -17,14 +15,9 @@ module Gitlab
Gitlab::Satellite::Logger.error(message)
end
- def raise_no_satellite
- raise SatelliteNotExistError.new("Satellite doesn't exist")
- end
-
def clear_and_update!
- raise_no_satellite unless exists?
+ project.ensure_satellite_exists
- File.exists? path
@repo = nil
clear_working_dir!
delete_heads!
@@ -33,7 +26,7 @@ module Gitlab
end
def create
- output, status = popen("git clone #{project.repository.path_to_repo} #{path}",
+ output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}),
Gitlab.config.satellites.path)
log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}")
@@ -55,7 +48,7 @@ module Gitlab
# * Changes the current directory to the satellite's working dir
# * Yields
def lock
- raise_no_satellite unless exists?
+ project.ensure_satellite_exists
File.open(lock_file, "w+") do |f|
begin
@@ -77,7 +70,7 @@ module Gitlab
end
def repo
- raise_no_satellite unless exists?
+ project.ensure_satellite_exists
@repo ||= Grit::Repo.new(path)
end
@@ -91,6 +84,7 @@ module Gitlab
# Clear the working directory
def clear_working_dir!
repo.git.reset(hard: true)
+ repo.git.clean(f: true, d: true, x: true)
end
# Deletes all branches except the parking branch
@@ -123,7 +117,7 @@ module Gitlab
remotes.each { |name| repo.git.remote(default_options,'rm', name)}
end
- # Updates the satellite from Gitolite
+ # Updates the satellite from bare repo
#
# Note: this will only update remote branches (i.e. origin/*)
def update_from_source!
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 3aa3b2ba1e..39de1223b1 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -1,10 +1,29 @@
module Gitlab
class Seeder
def self.quiet
+ mute_mailer
SeedFu.quiet = true
yield
SeedFu.quiet = false
puts "\nOK".green
end
+
+ def self.by_user(user)
+ begin
+ Thread.current[:current_user] = user
+ yield
+ ensure
+ Thread.current[:current_user] = nil
+ end
+ end
+
+ def self.mute_mailer
+ code = <<-eos
+def Notify.delay
+ self
+end
+ eos
+ eval(code)
+ end
end
end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
index 8960416230..44237a062f 100644
--- a/lib/gitlab/theme.rb
+++ b/lib/gitlab/theme.rb
@@ -15,7 +15,7 @@ module Gitlab
COLOR => "ui_color"
}
- id ||= 1
+ id ||= Gitlab.config.gitlab.default_theme
return themes[id]
end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
new file mode 100644
index 0000000000..0846359f9b
--- /dev/null
+++ b/lib/gitlab/upgrader.rb
@@ -0,0 +1,102 @@
+require_relative "popen"
+require_relative "version_info"
+
+module Gitlab
+ class Upgrader
+ def execute
+ puts "GitLab #{current_version.major} upgrade tool"
+ puts "Your version is #{current_version}"
+ puts "Latest available version for GitLab #{current_version.major} is #{latest_version}"
+
+ if latest_version?
+ puts "You are using the latest GitLab version"
+ else
+ puts "Newer GitLab version is available"
+ answer = if ARGV.first == "-y"
+ "yes"
+ else
+ prompt("Do you want to upgrade (yes/no)? ", %w{yes no})
+ end
+
+ if answer == "yes"
+ upgrade
+ else
+ exit 0
+ end
+ end
+ end
+
+ def latest_version?
+ current_version >= latest_version
+ end
+
+ def current_version
+ @current_version ||= Gitlab::VersionInfo.parse(current_version_raw)
+ end
+
+ def latest_version
+ @latest_version ||= Gitlab::VersionInfo.parse(latest_version_raw)
+ end
+
+ def current_version_raw
+ File.read(File.join(gitlab_path, "VERSION")).strip
+ end
+
+ def latest_version_raw
+ remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags origin))
+ git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
+ git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ }
+ last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s
+ end
+
+ def update_commands
+ {
+ "Stash changed files" => %W(git stash),
+ "Get latest code" => %W(git fetch),
+ "Switch to new version" => %W(git checkout v#{latest_version}),
+ "Install gems" => %W(bundle),
+ "Migrate DB" => %W(bundle exec rake db:migrate),
+ "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile),
+ "Clear cache" => %W(bundle exec rake cache:clear)
+ }
+ end
+
+ def env
+ {'RAILS_ENV' => 'production'}
+ end
+
+ def upgrade
+ update_commands.each do |title, cmd|
+ puts title
+ puts " -> #{cmd.join(' ')}"
+ if system(env, *cmd)
+ puts " -> OK"
+ else
+ puts " -> FAILED"
+ puts "Failed to upgrade. Try to repeat task or proceed with upgrade manually "
+ exit 1
+ end
+ end
+
+ puts "Done"
+ end
+
+ def gitlab_path
+ File.expand_path(File.join(File.dirname(__FILE__), '../..'))
+ end
+
+ # Prompt the user to input something
+ #
+ # message - the message to display before input
+ # choices - array of strings of acceptable answers or nil for any answer
+ #
+ # Returns the user's answer
+ def prompt(message, choices = nil)
+ begin
+ print(message)
+ answer = STDIN.gets.chomp
+ end while !choices.include?(answer)
+ answer
+ end
+ end
+end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
new file mode 100644
index 0000000000..16df21b49b
--- /dev/null
+++ b/lib/gitlab/user_access.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module UserAccess
+ def self.allowed?(user)
+ return false if user.blocked?
+
+ if Gitlab.config.ldap.enabled
+ if user.ldap_user?
+ # Check if LDAP user exists and match LDAP user_filter
+ Gitlab::LDAP::Access.open do |adapter|
+ return false unless adapter.allowed?(user)
+ end
+ end
+ end
+
+ true
+ end
+ end
+end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
new file mode 100644
index 0000000000..eada9bcddf
--- /dev/null
+++ b/lib/gitlab/visibility_level.rb
@@ -0,0 +1,42 @@
+# Gitlab::VisibilityLevel module
+#
+# Define allowed public modes that can be used for
+# GitLab projects to determine project public mode
+#
+module Gitlab
+ module VisibilityLevel
+ PRIVATE = 0
+ INTERNAL = 10
+ PUBLIC = 20
+
+ class << self
+ def values
+ options.values
+ end
+
+ def options
+ {
+ 'Private' => PRIVATE,
+ 'Internal' => INTERNAL,
+ 'Public' => PUBLIC
+ }
+ end
+
+ def allowed_for?(user, level)
+ user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level)
+ end
+ end
+
+ def private?
+ visibility_level_field == PRIVATE
+ end
+
+ def internal?
+ visibility_level_field == INTERNAL
+ end
+
+ def public?
+ visibility_level_field == PUBLIC
+ end
+ end
+end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index b84c005524..7d9428ff27 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -8,14 +8,11 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
@project = @template.instance_variable_get("@project")
@ref = @template.instance_variable_get("@ref")
@request_path = @template.instance_variable_get("@path")
+ @options = options.dup
super options
end
def block_code(code, language)
- options = { options: {encoding: 'utf-8'} }
- lexer = Pygments::Lexer.find(language) # language can be an alias
- options.merge!(lexer: lexer.aliases[0].downcase) if lexer # downcase is required
-
# New lines are placed to fix an rendering issue
# with code wrapped inside tag for next case:
#
@@ -25,7 +22,11 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
#
<<-HTML
-
#{Pygments.highlight(code, options)}
+
+
+
#{h.send(:html_escape, code)}
+
+
HTML
end
@@ -34,9 +35,21 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
h.link_to_gfm(content, link, title: title)
end
+ def header(text, level)
+ if @options[:no_header_anchors]
+ "#{text}"
+ else
+ id = ActionController::Base.helpers.strip_tags(h.gfm(text)).downcase() \
+ .gsub(/[^a-z0-9_-]/, '-').gsub(/-+/, '-').gsub(/^-/, '').gsub(/-$/, '')
+ "#{text}"
+ end
+ end
+
def preprocess(full_document)
- if @project
- h.create_relative_links(full_document, @project.path_with_namespace, @ref, @request_path, is_wiki?)
+ if is_wiki?
+ full_document
+ elsif @project
+ h.create_relative_links(full_document, @project, @ref, @request_path)
else
full_document
end
@@ -47,6 +60,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def is_wiki?
- @template.instance_variable_get("@wiki")
+ if @template.instance_variable_get("@project_wiki")
+ @template.instance_variable_get("@page")
+ end
end
end
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
index 0d2f8418bc..4684957233 100755
--- a/lib/support/deploy/deploy.sh
+++ b/lib/support/deploy/deploy.sh
@@ -28,17 +28,18 @@ sudo -u git -H git pull origin master
echo 'Deploy: Bundle and migrate'
# change it to your needs
-sudo -u git -H bundle --without postgres
+sudo -u git -H bundle --without aws development test mysql --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
# return stashed changes (if necessary)
# sudo -u git -H git stash pop
-
echo 'Deploy: Starting GitLab server...'
sudo service gitlab start
-sleep 10
sudo -u git -H rm /home/git/gitlab/public/index.html
echo 'Deploy: Done'
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index fbb7380ac4..3dd4465a6d 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -3,7 +3,6 @@
# GITLAB
# Maintainer: @randx
# Authors: rovanion.luckey@gmail.com, @randx
-# App Version: 6.0
### BEGIN INIT INFO
# Provides: gitlab
@@ -15,11 +14,20 @@
# Description: GitLab git repository management
### END INIT INFO
+
+###
+# DO NOT EDIT THIS FILE!
+# This file will be overwritten on update.
+# Instead add/change your variables in /etc/default/gitlab
+# An example defaults file can be found in lib/support/init.d/gitlab.default.example
+###
+
+
### Environment variables
RAILS_ENV="production"
-# Script variable names should be lower-case not to conflict with internal
-# /bin/sh variables such as PATH, EDITOR or SHELL.
+# Script variable names should be lower-case not to conflict with
+# internal /bin/sh variables such as PATH, EDITOR or SHELL.
app_user="git"
app_root="/home/$app_user/gitlab"
pid_path="$app_root/tmp/pids"
@@ -27,21 +35,20 @@ socket_path="$app_root/tmp/sockets"
web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
-
-
-### Here ends user configuration ###
-
+# Read configuration variable file if it is present
+test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ "$USER" != "$app_user" ]; then
- sudo -u "$app_user" -H -i $0 "$@"; exit;
+ eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit;
fi
-# Switch to the gitlab path, if it fails exit with an error.
+# Switch to the gitlab path, exit on failure.
if ! cd "$app_root" ; then
echo "Failed to cd into $app_root, exiting!"; exit 1
fi
+
### Init Script functions
## Gets the pids from the files
@@ -124,7 +131,7 @@ check_stale_pids(){
fi
fi
if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then
- echo "Removing stale Sidekiq web server pid. This is most likely caused by the Sidekiq crashing the last time it ran."
+ echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran."
if ! rm "$sidekiq_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
@@ -142,15 +149,15 @@ exit_if_not_running(){
}
## Starts Unicorn and Sidekiq if they're not running.
-start() {
+start_gitlab() {
check_stale_pids
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
echo -n "Starting both the GitLab Unicorn and Sidekiq"
elif [ "$web_status" != "0" ]; then
- echo -n "Starting GitLab Sidekiq"
- elif [ "$sidekiq_status" != "0" ]; then
echo -n "Starting GitLab Unicorn"
+ elif [ "$sidekiq_status" != "0" ]; then
+ echo -n "Starting GitLab Sidekiq"
fi
# Then check if the service is running. If it is: don't start again.
@@ -160,7 +167,7 @@ start() {
# Remove old socket if it exists
rm -f "$socket_path"/gitlab.socket 2>/dev/null
# Start the web server
- RAILS_ENV=$RAILS_ENV script/web start &
+ RAILS_ENV=$RAILS_ENV script/web start
fi
# If sidekiq is already running, don't start it again.
@@ -177,15 +184,15 @@ start() {
}
## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them.
-stop() {
+stop_gitlab() {
exit_if_not_running
if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
echo -n "Shutting down both Unicorn and Sidekiq"
elif [ "$web_status" = "0" ]; then
- echo -n "Shutting down Sidekiq"
- elif [ "$sidekiq_status" = "0" ]; then
echo -n "Shutting down Unicorn"
+ elif [ "$sidekiq_status" = "0" ]; then
+ echo -n "Shutting down Sidekiq"
fi
# If the Unicorn web server is running, tell it to stop;
@@ -239,7 +246,7 @@ print_status() {
}
## Tells unicorn to reload it's config and Sidekiq to restart
-reload(){
+reload_gitlab(){
exit_if_not_running
if [ "$wpid" = "0" ];then
echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
@@ -256,12 +263,12 @@ reload(){
}
## Restarts Sidekiq and Unicorn.
-restart(){
+restart_gitlab(){
check_status
if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
- stop
+ stop_gitlab
fi
- start
+ start_gitlab
}
@@ -269,16 +276,16 @@ restart(){
case "$1" in
start)
- start
+ start_gitlab
;;
stop)
- stop
+ stop_gitlab
;;
restart)
- restart
+ restart_gitlab
;;
reload|force-reload)
- reload
+ reload_gitlab
;;
status)
print_status
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
new file mode 100755
index 0000000000..9951bacedf
--- /dev/null
+++ b/lib/support/init.d/gitlab.default.example
@@ -0,0 +1,31 @@
+# Copy this lib/support/init.d/gitlab.default.example file to
+# /etc/default/gitlab in order for it to apply to your system.
+
+# RAILS_ENV defines the type of installation that is running.
+# Normal values are "production", "test" and "development".
+RAILS_ENV="production"
+
+# app_user defines the user that GitLab is run as.
+# The default is "git".
+app_user="git"
+
+# app_root defines the folder in which gitlab and it's components are installed.
+# The default is "/home/$app_user/gitlab"
+app_root="/home/$app_user/gitlab"
+
+# pid_path defines a folder in which the gitlab and it's components place their pids.
+# This variable is also used below to define the relevant pids for the gitlab components.
+# The default is "$app_root/tmp/pids"
+pid_path="$app_root/tmp/pids"
+
+# socket_path defines the folder in which gitlab places the sockets
+#The default is "$app_root/tmp/sockets"
+socket_path="$app_root/tmp/sockets"
+
+# web_server_pid_path defines the path in which to create the pid file fo the web_server
+# The default is "$pid_path/unicorn.pid"
+web_server_pid_path="$pid_path/unicorn.pid"
+
+# sidekiq_pid_path defines the path in which to create the pid file for sidekiq
+# The default is "$pid_path/sidekiq.pid"
+sidekiq_pid_path="$pid_path/sidekiq.pid"
diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab
index df9398d079..d9b07b61ec 100644
--- a/lib/support/logrotate/gitlab
+++ b/lib/support/logrotate/gitlab
@@ -2,21 +2,19 @@
# based on: http://stackoverflow.com/a/4883967
/home/git/gitlab/log/*.log {
- weekly
+ daily
missingok
- rotate 52
+ rotate 90
compress
- delaycompress
notifempty
copytruncate
}
/home/git/gitlab-shell/gitlab-shell.log {
- weekly
+ daily
missingok
- rotate 52
+ rotate 90
compress
- delaycompress
notifempty
copytruncate
}
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 3e929c5299..98c9163739 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -1,6 +1,19 @@
# GITLAB
# Maintainer: @randx
-# App Version: 5.0
+
+# CHUNKED TRANSFER
+# It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] which is not
+# supported by Nginx < 1.3.9 [1]. As a result, pushing a large object with Git (i.e. a single large file)
+# can lead to a 411 error. In theory you can get around this by tweaking this configuration file and either
+# - installing an old version of Nginx with the chunkin module [2] compiled in, or
+# - using a newer version of Nginx.
+#
+# At the time of writing we do not know if either of these theoretical solutions works. As a workaround
+# users can use Git over SSH to push large files.
+#
+# [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
+# [1] https://github.com/agentzh/chunkin-nginx-module#status
+# [2] https://github.com/agentzh/chunkin-nginx-module
upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket;
@@ -11,6 +24,10 @@ server {
server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
server_tokens off; # don't show the version number, a security best practice
root /home/git/gitlab/public;
+
+ # Increase this if you want to upload large attachments
+ # Or if you want to accept large git objects over http
+ client_max_body_size 20m;
# individual nginx logs for this gitlab vhost
access_log /var/log/nginx/gitlab_access.log;
@@ -25,15 +42,32 @@ server {
# if a file, which is not found in the root folder is requested,
# then the proxy pass the request to the upsteam (gitlab unicorn)
location @gitlab {
- proxy_read_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
- proxy_connect_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
+ # If you use https make sure you disable gzip compression
+ # to be safe against BREACH attack
+ # gzip off;
+
+ proxy_read_timeout 300; # Some requests take more than 30 seconds.
+ proxy_connect_timeout 300; # Some requests take more than 30 seconds.
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://gitlab;
}
-}
+ # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
+ # WARNING: If you are using relative urls do remove the block below
+ # See config/application.rb under "Relative url support" for the list of
+ # other files that need to be changed for relative url support
+ location ~ ^/(assets)/ {
+ root /home/git/gitlab/public;
+ gzip_static on; # to serve pre-gzipped version
+ expires max;
+ add_header Cache-Control public;
+ }
+
+ error_page 502 /502.html;
+}
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 7d3602211c..058c741704 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -1,10 +1,10 @@
+task dev: ["dev:setup"]
+
namespace :dev do
desc "GITLAB | Setup developer environment (db, fixtures)"
task :setup => :environment do
ENV['force'] = 'yes'
- Rake::Task["db:setup"].invoke
- Rake::Task["db:seed_fu"].invoke
+ Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
end
end
-
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index c270232edb..0e1a3d071e 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -15,10 +15,34 @@ namespace :gitlab do
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
+ user = User.find_by(email: args.email)
project_ids = Project.pluck(:id)
puts "Importing #{user.email} users into #{project_ids.size} projects"
UsersProject.add_users_into_projects(project_ids, Array.wrap(user.id), UsersProject::DEVELOPER)
end
+
+ desc "GITLAB | Add all users to all groups (admin users are added as owners)"
+ task all_users_to_all_groups: :environment do |t, args|
+ user_ids = User.where(admin: false).pluck(:id)
+ admin_ids = User.where(admin: true).pluck(:id)
+ groups = Group.all
+
+ puts "Importing #{user_ids.size} users into #{groups.size} groups"
+ puts "Importing #{admin_ids.size} admins into #{groups.size} groups"
+ groups.each do |group|
+ group.add_users(user_ids, UsersGroup::DEVELOPER)
+ group.add_users(admin_ids, UsersGroup::OWNER)
+ end
+ end
+
+ desc "GITLAB | Add a specific user to all groups (as a developer)"
+ task :user_to_groups, [:email] => :environment do |t, args|
+ user = User.find_by_email args.email
+ groups = Group.all
+ puts "Importing #{user.email} users into #{groups.size} groups"
+ groups.each do |group|
+ group.add_users(Array.wrap(user.id), UsersGroup::DEVELOPER)
+ end
+ end
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index f8c17f412b..bf015a1fe1 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -3,6 +3,7 @@ namespace :gitlab do
task check: %w{gitlab:env:check
gitlab:gitlab_shell:check
gitlab:sidekiq:check
+ gitlab:ldap:check
gitlab:app:check}
@@ -16,6 +17,7 @@ namespace :gitlab do
check_database_config_exists
check_database_is_not_sqlite
check_migrations_are_up
+ check_orphaned_users_groups
check_gitlab_config_exists
check_gitlab_config_not_outdated
check_log_writable
@@ -64,6 +66,7 @@ namespace :gitlab do
puts "no".green
else
puts "yes".red
+ puts "Please fix this by removing the SQLite entry from the database.yml".blue
for_more_information(
"https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL",
see_database_guide
@@ -167,7 +170,7 @@ namespace :gitlab do
def check_migrations_are_up
print "All migrations up? ... "
- migration_status = `bundle exec rake db:migrate:status`
+ migration_status, _ = Gitlab::Popen.popen(%W(bundle exec rake db:migrate:status))
unless migration_status =~ /down\s+\d{14}/
puts "yes".green
@@ -180,6 +183,19 @@ namespace :gitlab do
end
end
+ def check_orphaned_users_groups
+ print "Database contains orphaned UsersGroups? ... "
+ if UsersGroup.where("user_id not in (select id from users)").count > 0
+ puts "yes".red
+ try_fixing_it(
+ "You can delete the orphaned records using something along the lines of:",
+ sudo_gitlab("bundle exec rails runner -e production 'UsersGroup.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'")
+ )
+ else
+ puts "no".green
+ end
+ end
+
def check_satellites_exist
print "Projects have satellites? ... "
@@ -254,7 +270,7 @@ namespace :gitlab do
def check_redis_version
print "Redis version >= 2.0.0? ... "
- if run_and_match("redis-cli --version", /redis-cli 2.\d.\d/)
+ if run_and_match(%W(redis-cli --version), /redis-cli 2.\d.\d/)
puts "yes".green
else
puts "no".red
@@ -278,8 +294,6 @@ namespace :gitlab do
start_checking "Environment"
check_gitlab_git_config
- check_python2_exists
- check_python2_version
finished_checking "Environment"
end
@@ -296,7 +310,7 @@ namespace :gitlab do
"user.email" => Gitlab.config.gitlab.email_from
}
correct_options = options.map do |name, value|
- run("git config --global --get #{name}").try(:squish) == value
+ run(%W(git config --global --get #{name})).try(:squish) == value
end
if correct_options.all?
@@ -313,52 +327,6 @@ namespace :gitlab do
fix_and_rerun
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"
- )
- fix_and_rerun
- 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"
- )
- fix_and_rerun
- end
- end
end
@@ -374,6 +342,7 @@ namespace :gitlab do
check_repo_base_is_not_symlink
check_repo_base_user_and_group
check_repo_base_permissions
+ check_satellites_permissions
check_update_hook_is_up_to_date
check_repos_update_hooks_is_link
check_gitlab_shell_self_test
@@ -475,6 +444,29 @@ namespace :gitlab do
end
end
+ def check_satellites_permissions
+ print "Satellites access is drwxr-x---? ... "
+
+ satellites_path = Gitlab.config.satellites.path
+ unless File.exists?(satellites_path)
+ puts "can't check because of previous errors".magenta
+ return
+ end
+
+ if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
+ puts "yes".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "sudo chmod u+rwx,g+rx,o-rwx #{satellites_path}",
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab"
+ )
+ fix_and_rerun
+ end
+ end
+
def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
@@ -536,7 +528,7 @@ namespace :gitlab do
"sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}"
)
for_more_information(
- "#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh"
+ "#{gitlab_shell_path}/support/rewrite-hooks.sh"
)
fix_and_rerun
next
@@ -560,7 +552,7 @@ namespace :gitlab do
end
def check_gitlab_shell_self_test
- gitlab_shell_repo_base = File.expand_path('gitlab-shell', gitlab_shell_user_home)
+ gitlab_shell_repo_base = gitlab_shell_path
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
puts "Running #{check_cmd}"
if system(check_cmd, chdir: gitlab_shell_repo_base)
@@ -606,15 +598,12 @@ namespace :gitlab do
# Helper methods
########################
- def gitlab_shell_user_home
- File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
+ def gitlab_shell_path
+ Gitlab.config.gitlab_shell.path
end
def gitlab_shell_version
- gitlab_shell_version_file = "#{gitlab_shell_user_home}/gitlab-shell/VERSION"
- if File.readable?(gitlab_shell_version_file)
- File.read(gitlab_shell_version_file)
- end
+ Gitlab::Shell.new.version
end
def has_gitlab_shell3?
@@ -678,10 +667,64 @@ namespace :gitlab do
end
def sidekiq_process_count
- `ps ux`.scan(/sidekiq \d+\.\d+\.\d+/).count
+ ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
+ ps_ux.scan(/sidekiq \d+\.\d+\.\d+/).count
end
end
+ namespace :ldap do
+ task :check, [:limit] => :environment do |t, args|
+ # Only show up to 100 results because LDAP directories can be very big.
+ # This setting only affects the `rake gitlab:check` script.
+ args.with_defaults(limit: 100)
+ warn_user_is_not_gitlab
+ start_checking "LDAP"
+
+ if ldap_config.enabled
+ print_users(args.limit)
+ else
+ puts 'LDAP is disabled in config/gitlab.yml'
+ end
+
+ finished_checking "LDAP"
+ end
+
+ def print_users(limit)
+ puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
+ ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry|
+ puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}"
+ end
+ end
+
+ def attributes
+ [ldap_config.uid]
+ end
+
+ def filter
+ uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
+ if user_filter
+ Net::LDAP::Filter.join(uid_filter, user_filter)
+ else
+ uid_filter
+ end
+ end
+
+ def user_filter
+ if ldap_config['user_filter'] && ldap_config.user_filter.present?
+ Net::LDAP::Filter.construct(ldap_config.user_filter)
+ else
+ nil
+ end
+ end
+
+ def ldap
+ @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection
+ end
+
+ def ldap_config
+ @ldap_config ||= Gitlab.config.ldap
+ end
+ end
# Helper methods
##########################
@@ -736,7 +779,7 @@ namespace :gitlab do
end
def check_gitlab_shell
- required_version = Gitlab::VersionInfo.new(1, 7, 4)
+ required_version = Gitlab::VersionInfo.new(1, 9, 3)
current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
print "GitLab Shell version >= #{required_version} ? ... "
@@ -749,7 +792,7 @@ namespace :gitlab do
def check_git_version
required_version = Gitlab::VersionInfo.new(1, 7, 10)
- current_version = Gitlab::VersionInfo.parse(run("#{Gitlab.config.git.bin_path} --version"))
+ current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version)))
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
print "Git version >= #{required_version} ? ... "
diff --git a/lib/tasks/gitlab/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake
new file mode 100644
index 0000000000..a66030ab93
--- /dev/null
+++ b/lib/tasks/gitlab/db/drop_all_tables.rake
@@ -0,0 +1,10 @@
+namespace :gitlab do
+ namespace :db do
+ task drop_all_tables: :environment do
+ connection = ActiveRecord::Base.connection
+ connection.tables.each do |table|
+ connection.drop_table(table)
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake
index 6822e5cf1a..aa9869daf2 100644
--- a/lib/tasks/gitlab/enable_automerge.rake
+++ b/lib/tasks/gitlab/enable_automerge.rake
@@ -27,11 +27,8 @@ namespace :gitlab do
if project.satellite.exists?
puts "exists already".green
else
- puts ""
- project.satellite.create
-
- print "... "
- if $?.success?
+ print "\n... "
+ if project.satellite.create
puts "created".green
else
puts "error".red
diff --git a/lib/tasks/gitlab/enable_namespaces.rake b/lib/tasks/gitlab/enable_namespaces.rake
index 927748c0fd..201f34ab54 100644
--- a/lib/tasks/gitlab/enable_namespaces.rake
+++ b/lib/tasks/gitlab/enable_namespaces.rake
@@ -43,13 +43,13 @@ namespace :gitlab do
username.gsub!("+", ".")
# return username if no matches
- return username unless User.find_by_username(username)
+ return username unless User.find_by(username: username)
# look for same username
(1..10).each do |i|
suffixed_username = "#{username}#{i}"
- return suffixed_username unless User.find_by_username(suffixed_username)
+ return suffixed_username unless User.find_by(username: suffixed_username)
end
end
diff --git a/lib/tasks/gitlab/generate_docs.rake b/lib/tasks/gitlab/generate_docs.rake
index 58795fac4a..332cd61f84 100644
--- a/lib/tasks/gitlab/generate_docs.rake
+++ b/lib/tasks/gitlab/generate_docs.rake
@@ -1,7 +1,7 @@
namespace :gitlab do
desc "GITLAB | Generate sdocs for project"
task generate_docs: :environment do
- system("bundle exec sdoc -o doc/code app lib")
+ system(*%W(bundle exec sdoc -o doc/code app lib))
end
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 8fa8927085..cbfa736c84 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -2,12 +2,12 @@ namespace :gitlab do
namespace :import do
# How to use:
#
- # 1. copy your bare repos under git repos_path
- # 2. run bundle exec rake gitlab:import:repos RAILS_ENV=production
+ # 1. copy the bare repos under the repos_path (commonly /home/git/repositories)
+ # 2. run: bundle exec rake gitlab:import:repos RAILS_ENV=production
#
# Notes:
- # * project owner will be a first admin
- # * existing projects will be skipped
+ # * The project owner will set to the first administator of the system
+ # * Existing projects will be skipped
#
desc "GITLAB | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
task repos: :environment do
@@ -50,7 +50,7 @@ namespace :gitlab do
# find group namespace
if group_name
- group = Group.find_by_path(group_name)
+ group = Group.find_by(path: group_name)
# create group namespace
if !group
group = Group.new(:name => group_name)
@@ -66,7 +66,7 @@ namespace :gitlab do
project_params[:namespace_id] = group.id
end
- project = Projects::CreateContext.new(user, project_params).execute
+ project = Projects::CreateService.new(user, project_params).execute
if project.valid?
puts " * Created #{project.name} (#{repo_path})".green
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index ea83efcd88..72452e1d8e 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -4,26 +4,27 @@ namespace :gitlab do
task info: :environment do
# check if there is an RVM environment
- rvm_version = run_and_match("rvm --version", /[\d\.]+/).try(:to_s)
+ rvm_version = run_and_match(%W(rvm --version), /[\d\.]+/).try(:to_s)
# check Ruby version
- ruby_version = run_and_match("ruby --version", /[\d\.p]+/).try(:to_s)
+ ruby_version = run_and_match(%W(ruby --version), /[\d\.p]+/).try(:to_s)
# check Gem version
- gem_version = run("gem --version")
+ gem_version = run(%W(gem --version))
# check Bundler version
- bunder_version = run_and_match("bundle --version", /[\d\.]+/).try(:to_s)
+ bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
# check Bundler version
- rake_version = run_and_match("rake --version", /[\d\.]+/).try(:to_s)
+ rake_version = run_and_match(%W(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 "Current User:\t#{run(%W(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}"
+ puts "Sidekiq Version:#{Sidekiq::VERSION}"
# check database adapter
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 2b730774e0..8b4ccdfc3f 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -15,6 +15,7 @@ namespace :gitlab do
end
Rake::Task["db:setup"].invoke
+ Rake::Task["add_limits_mysql"].invoke
Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".red
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0d7a390bc9..dfc90bb333 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -1,5 +1,64 @@
namespace :gitlab do
namespace :shell do
+ desc "GITLAB | Install or upgrade gitlab-shell"
+ task :install, [:tag, :repo] => :environment do |t, args|
+ warn_user_is_not_gitlab
+
+ args.with_defaults(tag: "v1.9.3", repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
+
+ user = Settings.gitlab.user
+ home_dir = Settings.gitlab.user_home
+ gitlab_url = Settings.gitlab.url
+ # gitlab-shell requires a / at the end of the url
+ gitlab_url += "/" unless gitlab_url.match(/\/$/)
+ repos_path = Gitlab.config.gitlab_shell.repos_path
+ target_dir = Gitlab.config.gitlab_shell.path
+
+ # Clone if needed
+ unless File.directory?(target_dir)
+ sh "git clone '#{args.repo}' '#{target_dir}'"
+ end
+
+ # Make sure we're on the right tag
+ Dir.chdir(target_dir) do
+ sh "git fetch origin && git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})"
+
+ redis_url = URI.parse(ENV['REDIS_URL'] || "redis://localhost:6379")
+
+ config = {
+ user: user,
+ gitlab_url: gitlab_url,
+ http_settings: {self_signed_cert: false}.stringify_keys,
+ repos_path: repos_path,
+ auth_file: File.join(home_dir, ".ssh", "authorized_keys"),
+ redis: {
+ bin: %x{which redis-cli}.chomp,
+ host: redis_url.host,
+ port: redis_url.port,
+ namespace: "resque:gitlab"
+ }.stringify_keys,
+ log_level: "INFO",
+ audit_usernames: false
+ }.stringify_keys
+
+ # Generate config.yml based on existing gitlab settings
+ File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
+
+ # Launch installation process
+ sh "bin/install"
+ end
+
+ # Required for debian packaging with PKGR: Setup .ssh/environment with
+ # the current PATH, so that the correct ruby version gets loaded
+ # Requires to set "PermitUserEnvironment yes" in sshd config (should not
+ # be an issue since it is more than likely that there are no "normal"
+ # user accounts on a gitlab server). The alternative is for the admin to
+ # install a ruby (1.9.3+) in the global path.
+ File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f|
+ f.puts "PATH=#{ENV['PATH']}"
+ end
+ end
+
desc "GITLAB | Setup gitlab-shell"
task setup: :environment do
setup
@@ -34,14 +93,18 @@ namespace :gitlab do
Gitlab::Shell.new.remove_all_keys
- Key.find_each(batch_size: 1000) do |key|
- if Gitlab::Shell.new.add_key(key.shell_id, key.key)
+ Gitlab::Shell.new.batch_add_keys do |adder|
+ Key.find_each(batch_size: 1000) do |key|
+ adder.add_key(key.shell_id, key.key)
print '.'
- else
- print 'F'
end
end
+ unless $?.success?
+ puts "Failed to add keys...".red
+ exit 1
+ end
+
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".red
exit 1
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index ac2c4577c7..da61c6e007 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -2,6 +2,16 @@ module Gitlab
class TaskAbortedByUserError < StandardError; end
end
+unless STDOUT.isatty
+ module Colored
+ extend self
+
+ def colorize(string, options={})
+ string
+ end
+ end
+end
+
namespace :gitlab do
# Ask if the user wants to continue
@@ -18,7 +28,7 @@ namespace :gitlab do
# It will primarily use lsb_relase to determine the OS.
# It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
- os_name = run("lsb_release -irs")
+ os_name = run(%W(lsb_release -irs))
os_name ||= if File.readable?('/etc/system-release')
File.read('/etc/system-release')
end
@@ -29,7 +39,7 @@ namespace :gitlab do
os_name ||= if File.readable?('/etc/SuSE-release')
File.read('/etc/SuSE-release')
end
- os_name ||= if os_x_version = run("sw_vers -productVersion")
+ os_name ||= if os_x_version = run(%W(sw_vers -productVersion))
"Mac OS X #{os_x_version}"
end
os_name ||= if File.readable?('/etc/os-release')
@@ -70,13 +80,14 @@ namespace :gitlab do
#
# see also #run_and_match
def run(command)
- unless `#{command} 2>/dev/null`.blank?
- `#{command}`
- end
+ output, _ = Gitlab::Popen.popen(command)
+ output
+ rescue Errno::ENOENT
+ '' # if the command does not exist, return an empty string
end
def uid_for(user_name)
- run("id -u #{user_name}").chomp.to_i
+ run(%W(id -u #{user_name})).chomp.to_i
end
def gid_for(group_name)
@@ -90,7 +101,7 @@ namespace :gitlab do
def warn_user_is_not_gitlab
unless @warned_user_not_gitlab
gitlab_user = Gitlab.config.gitlab.user
- current_user = run("whoami").chomp
+ current_user = run(%W(whoami)).chomp
unless current_user == gitlab_user
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."
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index 011748c971..9516210e20 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -2,17 +2,15 @@ namespace :gitlab do
desc "GITLAB | Run all tests"
task :test do
cmds = [
- "rake db:setup",
- "rake db:seed_fu",
- "rake spinach",
- "rake spec",
- "rake jasmine:ci"
+ %W(rake spinach),
+ %W(rake spec),
+ %W(rake jasmine:ci)
]
cmds.each do |cmd|
- system(cmd + " RAILS_ENV=test")
+ result = system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
- raise "#{cmd} failed!" unless $?.exitstatus.zero?
+ raise "#{cmd} failed!" unless result
end
end
end
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
new file mode 100644
index 0000000000..a1972a682d
--- /dev/null
+++ b/lib/tasks/migrate/add_limits_mysql.rake
@@ -0,0 +1,7 @@
+require Rails.root.join('db/migrate/limits_to_mysql')
+
+desc "GITLAB | Add limits to strings in mysql database"
+task add_limits_mysql: :environment do
+ puts "Adding limits to schema.rb for mysql"
+ LimitsToMysql.new.up
+end
diff --git a/lib/tasks/setup.rake b/lib/tasks/setup.rake
new file mode 100644
index 0000000000..93701de8f6
--- /dev/null
+++ b/lib/tasks/setup.rake
@@ -0,0 +1,4 @@
+desc "GITLAB | Setup gitlab db"
+task :setup do
+ Rake::Task["gitlab:setup"].invoke
+end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index e91678473a..ba806e53cc 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,21 +1,21 @@
namespace :sidekiq do
desc "GITLAB | Stop sidekiq"
task :stop do
- system "script/background_jobs stop"
+ system *%W(script/background_jobs stop)
end
desc "GITLAB | Start sidekiq"
task :start do
- system "script/background_jobs start"
+ system *%W(script/background_jobs start)
end
desc 'GitLab | Restart sidekiq'
task :restart do
- system "script/background_jobs restart"
+ system *%W(script/background_jobs restart)
end
desc "GITLAB | Start sidekiq with launchd on Mac OS X"
task :launchd do
- system "script/background_jobs start_no_deamonize"
+ system *%W(script/background_jobs start_no_deamonize)
end
end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
new file mode 100644
index 0000000000..49fbe1bd47
--- /dev/null
+++ b/lib/tasks/spec.rake
@@ -0,0 +1,46 @@
+Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
+
+namespace :spec do
+ desc 'GITLAB | Run request specs'
+ task :api do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec --tag @api)
+ ]
+ run_commands(cmds)
+ end
+
+ desc 'GITLAB | Run feature specs'
+ task :feature do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec --tag @feature)
+ ]
+ run_commands(cmds)
+ end
+
+ desc 'GITLAB | Run other specs'
+ task :other do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec --tag ~@api --tag ~@feature)
+ ]
+ run_commands(cmds)
+ end
+end
+
+desc "GITLAB | Run specs"
+task :spec do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec),
+ ]
+ run_commands(cmds)
+end
+
+def run_commands(cmds)
+ cmds.each do |cmd|
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
+ raise "#{cmd} failed!" unless $?.exitstatus.zero?
+ end
+end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
new file mode 100644
index 0000000000..c23d0e0e18
--- /dev/null
+++ b/lib/tasks/spinach.rake
@@ -0,0 +1,14 @@
+Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach')
+
+desc "GITLAB | Run spinach"
+task :spinach do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(spinach),
+ ]
+
+ cmds.each do |cmd|
+ system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
+ raise "#{cmd} failed!" unless $?.exitstatus.zero?
+ end
+end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
new file mode 100644
index 0000000000..f19da1bb43
--- /dev/null
+++ b/lib/tasks/test.rake
@@ -0,0 +1,6 @@
+Rake::Task["test"].clear
+
+desc "GITLAB | Run all tests"
+task :test do
+ Rake::Task["gitlab:test"].invoke
+end
diff --git a/public/500.html b/public/500.html
index 5b78e3e38c..c84b9e90e4 100644
--- a/public/500.html
+++ b/public/500.html
@@ -8,6 +8,6 @@
500
We're sorry, but something went wrong.
- We've been notified about this issue and we'll take a look at it shortly.
+ Please contact your GitLab administrator if this problem persists.