Commit 6a00cf1b authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into show-status-from-branch

* upstream/master: (74 commits)
  Clarify the author field for the changelog documentation
  Add and update .gitignore & .gitlab-ci.yml templates for 8.14
  Update "Installation from source" guide for 8.14.0
  Add CHANGELOG entries for latest patches
  Merge branch 'fix/import-export-symlink-vulnerability' into 'security'
  Merge branch 'fix/import-projectmember-security' into 'security'
  Use stubs instead of modifying global states
  Add changelog instructions to CHANGELOG.md
  Try not to include anything globally!
  Update help banner for bin/changelog
  Add a `--force` option to bin/changelog
  Update examples in changelog docs to use single quotes around title
  Use the server's base URL without relative URL part when creating links in JIRA
  Update docs and test description
  Update docs and unexpose token
  Make ESLint ignore instrumented files for coverage analysis (!7236)
  Initialize form validation on new group form.
  Check that JavaScript file names match convention (!7238)
  Unchange username_validator.
  Move snake_case to camelCase.
  ...
parents f3c3d8e6 3ac3106e
/coverage-javascript/
/public/ /public/
/tmp/ /tmp/
/vendor/ /vendor/
......
{ {
"extends": "airbnb", "extends": "airbnb",
"plugins": [
"filenames"
],
"rules": {
"filenames/match-regex": [2, "^[a-z_]+$"]
},
"globals": { "globals": {
"$": false, "$": false,
"_": false, "_": false,
......
Please view this file on the master branch, on stable branches it's out of date. **Note:** This file is automatically generated. Please see the [developer
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 8.14.0 (2016-11-22) ## 8.14.0 (2016-11-22)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
- Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Adds user project membership expired event to clarify why user was removed (Callum Dryden)
- Trim leading and trailing whitespace on project_path (Linus Thiel) - Trim leading and trailing whitespace on project_path (Linus Thiel)
- Prevent award emoji via notes for issues/MRs authored by user (barthc) - Prevent award emoji via notes for issues/MRs authored by user (barthc)
- Adds support for the `token` attribute in project hooks API (Gauvain Pocentek)
- Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO) - Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO)
- Fix Markdown styling inside reference links (Jan Zdráhal) - Fix Markdown styling inside reference links (Jan Zdráhal)
- Fix extra space on Build sidebar on Firefox !7060 - Fix extra space on Build sidebar on Firefox !7060
- Fail gracefully when creating merge request with non-existing branch (alexsanford)
- Fix mobile layout issues in admin user overview page !7087 - Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd) - Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
- Refactor Jira service to use jira-ruby gem - Refactor Jira service to use jira-ruby gem
- Improved todos empty state - Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne) - Add hover to trash icon in notes !7008 (blackst0ne)
- Hides project activity tabs when features are disabled
- Only show one error message for an invalid email !5905 (lycoperdon) - Only show one error message for an invalid email !5905 (lycoperdon)
- Added guide describing how to upgrade PostgreSQL using Slony
- Fix sidekiq stats in admin area (blackst0ne) - Fix sidekiq stats in admin area (blackst0ne)
- Added label description as tooltip to issue board list title
- Created cycle analytics bundle JavaScript file - Created cycle analytics bundle JavaScript file
- API: Fix booleans not recognized as such when using the `to_boolean` helper - API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954 - Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne) - Stop unauthorized users dragging on milestone page (blackst0ne)
- Restore issue boards welcome message when a project is created !6899 - Restore issue boards welcome message when a project is created !6899
- Check that JavaScript file names match convention !7238 (winniehell)
- Do not show tooltip for active element !7105 (winniehell) - Do not show tooltip for active element !7105 (winniehell)
- Escape ref and path for relative links !6050 (winniehell) - Escape ref and path for relative links !6050 (winniehell)
- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose) - Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose)
- Fix broken issue/merge request links in JIRA comments. !6143 (Brian Kintz)
- Fix filtering of milestones with quotes in title (airatshigapov) - Fix filtering of milestones with quotes in title (airatshigapov)
- Fix issue boards dragging bug in Safari
- Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison) - Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison)
- Fix showing pipeline status for a given commit from correct branch !7034 - Fix showing pipeline status for a given commit from correct branch !7034
- Update mail_room and enable sentinel support to Reply By Email (!7101) - Update mail_room and enable sentinel support to Reply By Email (!7101)
...@@ -33,24 +46,28 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -33,24 +46,28 @@ Please view this file on the master branch, on stable branches it's out of date.
- New issue board list dropdown stays open after adding a new list - New issue board list dropdown stays open after adding a new list
- Fix: Backup restore doesn't clear cache - Fix: Backup restore doesn't clear cache
- Optimize Event queries by removing default order - Optimize Event queries by removing default order
- Remove duplicate links from sidebar
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh) - API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
- Add Rake task to create/repair GitLab Shell hooks symlinks !5634
- Add job for removal of unreferenced LFS objects from both the database and the filesystem (Frank Groeneveld) - Add job for removal of unreferenced LFS objects from both the database and the filesystem (Frank Groeneveld)
- Replace jquery.cookie plugin with js.cookie !7085 - Replace jquery.cookie plugin with js.cookie !7085
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens - Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens
- Show full status link on MR & commit pipelines - Show full status link on MR & commit pipelines
- Fix documents and comments on Build API `scope` - Fix documents and comments on Build API `scope`
- Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- Shortened merge request modal to let clipboard button not overlap - Shortened merge request modal to let clipboard button not overlap
- Adds JavaScript validation for group path editing field
- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
- Improve search query parameter naming in /admin/users !7115 (YarNayar) - Improve search query parameter naming in /admin/users !7115 (YarNayar)
- Fix table pagination to be responsive
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar) - Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
## 8.13.3 ## 8.13.3 (2016-11-02)
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218 - Removes any symlinks before importing a project export file. CVE-2016-9086
- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project - Fixed Import/Export foreign key issue to do with project members.
- Fix project features default values
## 8.13.2 (2016-10-31) ## 8.13.2 (2016-10-31)
...@@ -240,6 +257,11 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -240,6 +257,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix broken Project API docs (Takuya Noguchi) - Fix broken Project API docs (Takuya Noguchi)
- Migrate invalid project members (owner -> master) - Migrate invalid project members (owner -> master)
## 8.12.8 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
## 8.12.7 ## 8.12.7
- Prevent running `GfmAutocomplete` setup for each diff note. !6569 - Prevent running `GfmAutocomplete` setup for each diff note. !6569
...@@ -499,6 +521,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -499,6 +521,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix non-master branch readme display in tree view - Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs - Add UX improvements for merge request version diffs
## 8.11.10 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.11.9 ## 8.11.9
- Don't send Private-Token (API authentication) headers to Sentry - Don't send Private-Token (API authentication) headers to Sentry
...@@ -739,6 +765,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -739,6 +765,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - Simplify SQL queries of marking a todo as done
## 8.10.13 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.10.12 ## 8.10.12
- Don't send Private-Token (API authentication) headers to Sentry - Don't send Private-Token (API authentication) headers to Sentry
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
- [Technical debt](#technical-debt) - [Technical debt](#technical-debt)
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
- [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases) - [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done) - [Definition of done](#definition-of-done)
...@@ -247,13 +246,7 @@ request is as follows: ...@@ -247,13 +246,7 @@ request is as follows:
1. Fork the project into your personal space on GitLab.com 1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `master` 1. Create a feature branch, branch away from `master`
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG.md](CHANGELOG.md): 1. [Generate a changelog entry with `bin/changelog`][changelog]
1. If you are fixing a ~regression issue, you can add your entry to the next
patch release (e.g. `8.12.5` if current version is `8.12.4`)
1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
current version is `8.12.4`
1. Please add your entry at a random place among the entries of the targeted
release
1. If you are writing documentation, make sure to follow the 1. If you are writing documentation, make sure to follow the
[documentation styleguide][doc-styleguide] [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by 1. If you have multiple commits please combine them into one commit by
...@@ -262,8 +255,11 @@ request is as follows: ...@@ -262,8 +255,11 @@ request is as follows:
1. Submit a merge request (MR) to the `master` branch 1. Submit a merge request (MR) to the `master` branch
1. The MR title should describe the change you want to make 1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you 1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format] used to achieve it.
(#merge-request-description-format) 1. If you are contributing code, fill in the template already provided in the
"Description" field.
1. If you are contributing documentation, choose `Documentation` from the
"Choose a template" menu and fill in the template.
1. If the MR changes the UI it should include *Before* and *After* screenshots 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, 1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R` `grep css-class ./app -R`
...@@ -469,6 +465,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -469,6 +465,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[contributor-covenant]: http://contributor-covenant.org [contributor-covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout [rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[changelog]: doc/development/changelog.md "Generate a changelog entry"
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
......
...@@ -117,7 +117,7 @@ gem 'truncato', '~> 0.7.8' ...@@ -117,7 +117,7 @@ gem 'truncato', '~> 0.7.8'
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2' gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.1.0'
# Application server # Application server
group :unicorn do group :unicorn do
...@@ -196,7 +196,7 @@ gem 'loofah', '~> 2.0.3' ...@@ -196,7 +196,7 @@ gem 'loofah', '~> 2.0.3'
gem 'licensee', '~> 8.0.0' gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing # Protect against bruteforcing
gem 'rack-attack', '~> 4.3.1' gem 'rack-attack', '~> 4.4.1'
# Ace editor # Ace editor
gem 'ace-rails-ap', '~> 4.1.0' gem 'ace-rails-ap', '~> 4.1.0'
......
...@@ -180,7 +180,7 @@ GEM ...@@ -180,7 +180,7 @@ GEM
railties railties
rotp (~> 2.0) rotp (~> 2.0)
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.7) diffy (3.1.0)
docile (1.1.5) docile (1.1.5)
doorkeeper (4.2.0) doorkeeper (4.2.0)
railties (>= 4.2) railties (>= 4.2)
...@@ -520,7 +520,7 @@ GEM ...@@ -520,7 +520,7 @@ GEM
rack (1.6.4) rack (1.6.4)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.3.1) rack-attack (4.4.1)
rack rack
rack-cors (0.4.0) rack-cors (0.4.0)
rack-mount (0.8.3) rack-mount (0.8.3)
...@@ -847,7 +847,7 @@ DEPENDENCIES ...@@ -847,7 +847,7 @@ DEPENDENCIES
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3) diffy (~> 3.1.0)
doorkeeper (~> 4.2.0) doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8) email_reply_parser (~> 0.5.8)
...@@ -929,7 +929,7 @@ DEPENDENCIES ...@@ -929,7 +929,7 @@ DEPENDENCIES
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.0)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.3.1) rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.7.1) rails (= 4.2.7.1)
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
fallbackClass: 'is-dragging', fallbackClass: 'is-dragging',
fallbackOnBody: true, fallbackOnBody: true,
ghostClass: 'is-ghost', ghostClass: 'is-ghost',
filter: '.has-tooltip, .btn', filter: '.board-delete, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 50, delay: gl.issueBoards.touchEnabled ? 100 : 50,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20, scrollSpeed: 20,
......
...@@ -299,7 +299,7 @@ ...@@ -299,7 +299,7 @@
}; };
Dispatcher.prototype.initFieldErrors = function() { Dispatcher.prototype.initFieldErrors = function() {
$('.show-gl-field-errors').each((i, form) => { $('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form); new gl.GlFieldErrors(form);
}); });
}; };
......
/* eslint-disable no-param-reassign */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `gl-show-field-errors` to the form element, and ensure error messages are
* declared in each inputs' `title` attribute. If no title is declared for an invalid
* field the user attempts to submit, "This field is required." will be shown by default.
*
* Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
*
* Set a custom error anchor for error message to be injected after with the
* class `gl-field-error-anchor`
*
* Examples:
*
* Basic:
*
* <form class='gl-show-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
* </form>
*
* Ignore specific inputs (e.g. UsernameValidator):
*
* <form class='gl-show-field-errors'>
* <div class="form-group>
* <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
* </div>
* <div class="form-group">
* <input type='text' name='username' title='Username is required.'/>
* </div>
* </form>
*
* Custom Error Anchor (allows error message to be injected after specified element):
*
* <form class='gl-show-field-errors'>
* <div class="form-group gl-field-error-anchor">
* <input type='text' name='username' title='Username is required.'/>
* // Error message typically injected here
* </div>
* // Error message now injected here
* </form>
*
* */
/*
* Regex Patterns in use:
*
* Only alphanumeric: : "[a-zA-Z0-9]+"
* No special characters : "[a-zA-Z0-9-_]+",
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
this.state = {
valid: false,
empty: true,
};
this.initFieldValidation();
}
initFieldValidation() {
const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
// hidden when injected into DOM
errorAnchor.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
this.renderValid();
} else if (this.state.empty) {
this.renderEmpty();
} else if (!this.state.valid) {
this.renderInvalid();
}
}
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() {
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() {
const inputVal = this.accessCurrentValue();
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
global.GlFieldError = GlFieldError;
})(window.gl || (window.gl = {}));
/* eslint-disable */ /* eslint-disable */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `show-gl-field-errors` to the form element, and ensure error messages are
* declared in each inputs' title attribute.
*
* Example:
*
* <form class='show-gl-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
*</form>
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
this.state = {
valid: false,
empty: true
};
this.initFieldValidation();
}
initFieldValidation() {
// hidden when injected into DOM
this.inputElement.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
const ignoreSelector = '.validation-ignore';
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
return this.renderValid();
}
if (this.state.empty) {
return this.renderEmpty();
}
if (!this.state.valid) {
return this.renderInvalid();
}
}
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.field_validator')
.on('keyup.field_validator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() { //= require gl_field_error
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() { ((global) => {
const inputVal = this.accessCurrentValue(); const customValidationFlag = 'gl-field-error-ignore';
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
const customValidationFlag = 'no-gl-field-errors';
class GlFieldErrors { class GlFieldErrors {
constructor(form) { constructor(form) {
...@@ -144,7 +22,7 @@ ...@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray() this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag)) .filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new GlFieldError({ input, formErrors: this })); .map((input) => new global.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit); this.form.on('submit', this.catchInvalidFormSubmit);
} }
......
...@@ -238,8 +238,11 @@ ...@@ -238,8 +238,11 @@
_this.expandViewContainer(); _this.expandViewContainer();
} }
_this.diffsLoaded = true; _this.diffsLoaded = true;
var anchoredDiff = gl.utils.getLocationHash();
if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() {
_this.scrollToElement("#diffs"); _this.scrollToElement("#diffs");
_this.highlighSelectedLine(); _this.highlighSelectedLine();
});
_this.filesCommentButton = $('.files .diff-file').filesCommentButton(); _this.filesCommentButton = $('.files .diff-file').filesCommentButton();
return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) { return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) {
e.preventDefault(); e.preventDefault();
...@@ -252,6 +255,17 @@ ...@@ -252,6 +255,17 @@
}); });
}; };
MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) {
var diffTitle = $('#file-path-' + anchoredDiff);
var diffFile = diffTitle.closest('.diff-file');
var nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
diffFile.singleFileDiff(true, cb);
} else {
cb();
}
};
MergeRequestTabs.prototype.highlighSelectedLine = function() { MergeRequestTabs.prototype.highlighSelectedLine = function() {
var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight; var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
$('.hll').removeClass('hll'); $('.hll').removeClass('hll');
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) { function SingleFileDiff(file, forceLoad, cb) {
this.file = file; this.file = file;
this.toggleDiff = bind(this.toggleDiff, this); this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
...@@ -32,9 +32,12 @@ ...@@ -32,9 +32,12 @@
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
if (forceLoad) {
this.toggleDiff(null, cb);
}
} }
SingleFileDiff.prototype.toggleDiff = function(e) { SingleFileDiff.prototype.toggleDiff = function(e, cb) {
var $target = $(e.target); var $target = $(e.target);
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
...@@ -54,11 +57,11 @@ ...@@ -54,11 +57,11 @@
} }
} else { } else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(); return this.getContentHTML(cb);
} }
}; };
SingleFileDiff.prototype.getContentHTML = function() { SingleFileDiff.prototype.getContentHTML = function(cb) {
this.collapsedContent.hide(); this.collapsedContent.hide();
this.loadingContent.show(); this.loadingContent.show();
$.get(this.diffForPath, (function(_this) { $.get(this.diffForPath, (function(_this) {
...@@ -76,6 +79,8 @@ ...@@ -76,6 +79,8 @@
if (typeof DiffNotesApp !== 'undefined') { if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents(); DiffNotesApp.compileComponents();
} }
if (cb) cb();
}; };
})(this)); })(this));
}; };
...@@ -84,10 +89,10 @@ ...@@ -84,10 +89,10 @@
})(); })();
$.fn.singleFileDiff = function() { $.fn.singleFileDiff = function(forceLoad, cb) {
return this.each(function() { return this.each(function() {
if (!$.data(this, 'singleFileDiff')) { if (!$.data(this, 'singleFileDiff') || forceLoad) {
return $.data(this, 'singleFileDiff', new SingleFileDiff(this)); return $.data(this, 'singleFileDiff', new SingleFileDiff(this, forceLoad, cb));
} }
}); });
}; };
......
...@@ -11,11 +11,9 @@ ...@@ -11,11 +11,9 @@
$this.parent().find('.star-count').text(data.star_count); $this.parent().find('.star-count').text(data.star_count);
if (isStarred) { if (isStarred) {
$starSpan.removeClass('starred').text('Star'); $starSpan.removeClass('starred').text('Star');
gl.utils.updateTooltipTitle($this, 'Star project');
$starIcon.removeClass('fa-star').addClass('fa-star-o'); $starIcon.removeClass('fa-star').addClass('fa-star-o');
} else { } else {
$starSpan.addClass('starred').text('Unstar'); $starSpan.addClass('starred').text('Unstar');
gl.utils.updateTooltipTitle($this, 'Unstar project');
$starIcon.removeClass('fa-star-o').addClass('fa-star'); $starIcon.removeClass('fa-star-o').addClass('fa-star');
} }
}; };
......
.avatar { @mixin avatar-size($size, $margin-right) {
width: $size;
height: $size;
margin-right: $margin-right;
}
.avatar-container {
float: left; float: left;
margin-right: 12px; margin-right: 15px;
border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.s16 { @include avatar-size(16px, 6px); }
&.s20 { @include avatar-size(20px, 7px); }
&.s24 { @include avatar-size(24px, 8px); }
&.s26 { @include avatar-size(26px, 8px); }
&.s32 { @include avatar-size(32px, 10px); }
&.s36 { @include avatar-size(36px, 10px); }
&.s40 { @include avatar-size(40px, 10px); }
&.s46 { @include avatar-size(46px, 15px); }
&.s48 { @include avatar-size(48px, 10px); }
&.s60 { @include avatar-size(60px, 12px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
&.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); }
}
.avatar {
@extend .avatar-container;
width: 40px; width: 40px;
height: 40px; height: 40px;
padding: 0; padding: 0;
border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline { &.avatar-inline {
float: none; float: none;
...@@ -20,22 +45,6 @@ ...@@ -20,22 +45,6 @@
border-radius: 0; border-radius: 0;
border: none; border: none;
} }
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
&.s20 { width: 20px; height: 20px; margin-right: 7px; }
&.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s70 { width: 70px; height: 70px; margin-right: 14px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
} }
.identicon { .identicon {
...@@ -54,3 +63,17 @@ ...@@ -54,3 +63,17 @@
&.s140 { font-size: 72px; line-height: 138px; } &.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; } &.s160 { font-size: 96px; line-height: 158px; }
} }
.image-container {
@extend .avatar-container;
overflow: hidden;
display: flex;
.avatar {
border-radius: 0;
border: none;
height: auto;
margin: 0;
align-self: center;
}
}
\ No newline at end of file
...@@ -136,3 +136,35 @@ label { ...@@ -136,3 +136,35 @@ label {
color: $red-normal; color: $red-normal;
} }
.gl-show-field-errors {
.gl-field-success-outline {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.gl-field-success-message {
color: $green-normal;
}
.gl-field-error-message {
color: $red-normal;
}
.gl-field-hint {
color: $gl-text-color;
}
}
...@@ -21,7 +21,14 @@ ...@@ -21,7 +21,14 @@
background: $color-darker; background: $color-darker;
} }
.nav-sidebar li { .sidebar-header,
.sidebar-action-buttons {
color: $color-light;
background-color: lighten($color-darker, 5%);
}
.nav-sidebar {
li {
a { a {
color: $color-light; color: $color-light;
...@@ -73,6 +80,8 @@ ...@@ -73,6 +80,8 @@
} }
} }
} }
}
} }
} }
......
...@@ -49,12 +49,16 @@ header { ...@@ -49,12 +49,16 @@ header {
font-size: 18px; font-size: 18px;
padding: 0; padding: 0;
margin: ($header-height - 28) / 2 0; margin: ($header-height - 28) / 2 0;
margin-left: 10px; margin-left: 8px;
height: 28px; height: 28px;
min-width: 28px; min-width: 28px;
line-height: 28px; line-height: 28px;
text-align: center; text-align: center;
&.header-user-dropdown-toggle {
margin-left: 14px;
}
&:hover, &:hover,
&:focus, &:focus,
&:active { &:active {
......
...@@ -142,10 +142,6 @@ ul.content-list { ...@@ -142,10 +142,6 @@ ul.content-list {
} }
} }
.avatar {
margin-right: 15px;
}
.controls { .controls {
float: right; float: right;
......
...@@ -7,8 +7,70 @@ ...@@ -7,8 +7,70 @@
.pagination { .pagination {
padding: 0; padding: 0;
} }
.gap,
.gap:hover {
background-color: $gray-light;
padding: $gl-vert-padding;
cursor: default;
}
} }
.panel > .gl-pagination { .panel > .gl-pagination {
margin: 0; margin: 0;
} }
/**
* Extra-small screen pagination.
*/
@media (max-width: 320px) {
.gl-pagination {
.first,
.last {
display: none;
}
.page {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Small screen pagination
*/
@media (max-width: $screen-xs) {
.gl-pagination {
.pagination li a {
padding: 6px 10px;
}
.page {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Medium screen pagination
*/
@media (min-width: $screen-xs) and (max-width: $screen-md-max) {
.gl-pagination {
.page {
display: none;
&.active,
&.sibling {
display: inline;
}
}
}
}
...@@ -59,6 +59,11 @@ ...@@ -59,6 +59,11 @@
padding: 0 !important; padding: 0 !important;
} }
.sidebar-header {
padding: 11px 22px 12px;
font-size: 20px;
}
li { li {
&.separate-item { &.separate-item {
padding-top: 10px; padding-top: 10px;
......
...@@ -12,6 +12,10 @@ ...@@ -12,6 +12,10 @@
opacity: 1!important; opacity: 1!important;
* { * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging // !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important; cursor: -webkit-grabbing!important;
cursor: grabbing!important; cursor: grabbing!important;
...@@ -45,11 +49,6 @@ ...@@ -45,11 +49,6 @@
.page-with-sidebar { .page-with-sidebar {
padding-bottom: 0; padding-bottom: 0;
} }
.issues-filters {
position: relative;
z-index: 999999;
}
} }
.boards-app { .boards-app {
......
...@@ -52,10 +52,25 @@ ...@@ -52,10 +52,25 @@
.build-header { .build-header {
position: relative; position: relative;
padding: 0;
display: flex;
min-height: 58px;
align-items: center;
.btn-inverted {
@include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light);
}
@media (max-width: $screen-sm-max) {
padding-right: 40px; padding-right: 40px;
@media (min-width: $screen-sm-min) { .btn-inverted {
padding-right: 0; display: none;
}
}
.header-content {
flex: 1;
} }
a { a {
...@@ -137,10 +152,15 @@ ...@@ -137,10 +152,15 @@
.retry-link { .retry-link {
color: $gl-link-color; color: $gl-link-color;
display: none;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
@media (max-width: $screen-sm-max) {
display: block;
}
} }
.stage-item { .stage-item {
......
...@@ -36,10 +36,6 @@ ...@@ -36,10 +36,6 @@
} }
} }
.dash-project-avatar {
float: left;
}
.dash-project-access-icon { .dash-project-access-icon {
float: left; float: left;
margin-right: 5px; margin-right: 5px;
......
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
} }
.dashboard .side .panel .panel-heading .input-group { .dashboard .side .panel .panel-heading .input-group {
.form-control { .form-control {
height: 42px; height: 42px;
} }
} }
.group-row { .group-row {
.stats { .stats {
float: right; float: right;
line-height: $list-text-height; line-height: $list-text-height;
...@@ -21,12 +23,14 @@ ...@@ -21,12 +23,14 @@
} }
.ldap-group-links { .ldap-group-links {
.form-actions { .form-actions {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
} }
.groups-cover-block { .groups-cover-block {
.container-fluid { .container-fluid {
position: relative; position: relative;
} }
...@@ -41,9 +45,14 @@ ...@@ -41,9 +45,14 @@
background-color: $background-color; background-color: $background-color;
} }
} }
.group-avatar {
border: 0;
}
} }
.groups-header { .groups-header {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.nav-links { .nav-links {
width: 35%; width: 35%;
......
...@@ -75,43 +75,17 @@ ...@@ -75,43 +75,17 @@
.login-body { .login-body {
font-size: 13px; font-size: 13px;
input + p { input + p {
margin-top: 5px; margin-top: 5px;
} }
.gl-field-success-outline { .username .validation-success {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.username .validation-success,
.gl-field-success-message {
color: $green-normal; color: $green-normal;
} }
.username .validation-error, .username .validation-error {
.gl-field-error-message {
color: $red-normal; color: $red-normal;
} }
.gl-field-hint {
color: $gl-text-color;
}
} }
} }
......
...@@ -96,8 +96,8 @@ ...@@ -96,8 +96,8 @@
.project-avatar { .project-avatar {
float: none; float: none;
margin-left: auto; margin: 0 auto;
margin-right: auto; border: none;
&.identicon { &.identicon {
border-radius: 50%; border-radius: 50%;
......
...@@ -39,6 +39,12 @@ module EventsHelper ...@@ -39,6 +39,12 @@ module EventsHelper
end end
end end
def event_filter_visible(feature_key)
return true unless @project
@project.feature_available?(feature_key, current_user)
end
def event_preposition(event) def event_preposition(event)
if event.push? || event.commented? || event.target if event.push? || event.commented? || event.target
"at" "at"
......
...@@ -237,7 +237,7 @@ class JiraService < IssueTrackerService ...@@ -237,7 +237,7 @@ class JiraService < IssueTrackerService
end end
def resource_url(resource) def resource_url(resource)
"#{Settings.gitlab['url'].chomp("/")}#{resource}" "#{Settings.gitlab.base_url.chomp("/")}#{resource}"
end end
def build_entity_url(entity_name, entity_id) def build_entity_url(entity_name, entity_id)
......
...@@ -2,11 +2,11 @@ class ProjectPolicy < BasePolicy ...@@ -2,11 +2,11 @@ class ProjectPolicy < BasePolicy
def rules def rules
team_access!(user) team_access!(user)
owner = user.admin? || owner = project.owner == user ||
project.owner == user ||
(project.group && project.group.has_owner?(user)) (project.group && project.group.has_owner?(user))
owner_access! if owner owner_access! if user.admin? || owner
team_member_owner_access! if owner
if project.public? || (project.internal? && !user.external?) if project.public? || (project.internal? && !user.external?)
guest_access! guest_access!
...@@ -16,7 +16,7 @@ class ProjectPolicy < BasePolicy ...@@ -16,7 +16,7 @@ class ProjectPolicy < BasePolicy
can! :read_build if project.public_builds? can! :read_build if project.public_builds?
if project.request_access_enabled && if project.request_access_enabled &&
!(owner || project.team.member?(user) || project_group_member?(user)) !(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access can! :request_access
end end
end end
...@@ -135,6 +135,10 @@ class ProjectPolicy < BasePolicy ...@@ -135,6 +135,10 @@ class ProjectPolicy < BasePolicy
can! :destroy_issue can! :destroy_issue
end end
def team_member_owner_access!
team_member_reporter_access!
end
# Push abilities on the users team role # Push abilities on the users team role
def team_access!(user) def team_access!(user)
access = project.team.max_member_access(user.id) access = project.team.max_member_access(user.id)
......
...@@ -13,20 +13,8 @@ module MergeRequests ...@@ -13,20 +13,8 @@ module MergeRequests
merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
if merge_request.target_branch.blank? || merge_request.source_branch.blank? messages = validate_branches(merge_request)
message = return build_failed(merge_request, messages) unless messages.empty?
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
return build_failed(merge_request, message)
end
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
return build_failed(merge_request, 'You must select different branches')
end
compare = CompareService.new.execute( compare = CompareService.new.execute(
merge_request.source_project, merge_request.source_project,
...@@ -43,6 +31,34 @@ module MergeRequests ...@@ -43,6 +31,34 @@ module MergeRequests
private private
def validate_branches(merge_request)
messages = []
if merge_request.target_branch.blank? || merge_request.source_branch.blank?
messages <<
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
end
if merge_request.source_project == merge_request.target_project &&
merge_request.target_branch == merge_request.source_branch
messages << 'You must select different branches'
end
# See if source and target branches exist
unless merge_request.source_project.commit(merge_request.source_branch)
messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
end
unless merge_request.target_project.commit(merge_request.target_branch)
messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
end
messages
end
# When your branch name starts with an iid followed by a dash this pattern will be # When your branch name starts with an iid followed by a dash this pattern will be
# interpreted as the user wants to close that issue on this project. # interpreted as the user wants to close that issue on this project.
# #
...@@ -91,8 +107,10 @@ module MergeRequests ...@@ -91,8 +107,10 @@ module MergeRequests
merge_request merge_request
end end
def build_failed(merge_request, message) def build_failed(merge_request, messages)
merge_request.errors.add(:base, message) unless message.nil? messages.compact.each do |message|
merge_request.errors.add(:base, message)
end
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request merge_request
......
= render 'devise/shared/tab_single', tab_title: 'Sign in preview' = render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box .login-box
%form.show-gl-field-errors %form.gl-show-field-errors
.form-group .form-group
= label_tag :login = label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false) = visibility_level_icon(group.visibility_level, fw: false)
.image-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs" = image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title .title
= link_to [:admin, group], class: 'group-name' do = link_to [:admin, group], class: 'group-name' do
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
Group info: Group info:
%ul.well-list %ul.well-list
%li %li
.image-container.s60
= image_tag group_icon(@group), class: "avatar s60" = image_tag group_icon(@group), class: "avatar s60"
%li %li
%span.light Name: %span.light Name:
......
...@@ -76,6 +76,7 @@ ...@@ -76,6 +76,7 @@
.title .title
= link_to [:admin, project.namespace.becomes(Namespace), project] do = link_to [:admin, project.namespace.becomes(Namespace), project] do
.dash-project-avatar .dash-project-avatar
.image-container.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
......
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' = render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group .form-group
......
= render 'devise/shared/tab_single', tab_title:'Change your password' = render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
= f.hidden_field :reset_password_token = f.hidden_field :reset_password_token
......
= render 'devise/shared/tab_single', tab_title: 'Reset Password' = render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group .form-group
......
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| = form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
%div.form-group %div.form-group
= f.label "Username or email", for: :login = f.label "Username or email", for: :login
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
......
= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do = form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do
.form-group .form-group
= label_tag :username, 'Username or email' = label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
......
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group .form-group
= label_tag :username, "#{server['label']} Username" = label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.login-box .login-box
.login-body .login-body
- if @user.two_factor_otp_enabled? - if @user.two_factor_otp_enabled?
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params - resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div %div
......
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } #register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body .login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
%div.form-group %div.form-group
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required." = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group %div.username.form-group
= f.label :username = f.label :username
= f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken. %p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available. %p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability... %p.validation-pending.hide Checking username availability...
......
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' = render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group.append-bottom-20 .form-group.append-bottom-20
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
.panel-heading .panel-heading
Group settings Group settings
.panel-body .panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f|
= form_errors(@group) = form_errors(@group)
= render 'shared/group_form', f: f = render 'shared/group_form', f: f
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.image-container.s160
= image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
%p.light %p.light
- if @group.avatar? - if @group.avatar?
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
New Group New Group
%hr %hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f| = form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f|
= form_errors(@group) = form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true = render 'shared/group_form', f: f, autofocus: true
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
.cover-block.groups-cover-block .cover-block.groups-cover-block
%div{ class: container_class } %div{ class: container_class }
= image_tag group_icon(@group), class: "avatar group-avatar s70 avatar-tile" .image-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile"
.group-info .group-info
.cover-title .cover-title
%h1 %h1
......
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li{class: "page"} %li
%span.page.gap %span.gap
= raw(t 'views.pagination.truncate') = raw(t 'views.pagination.truncate')
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li{class: "page#{' active' if page.current?}"} %li{class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}"}
= link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
...@@ -29,10 +29,6 @@ ...@@ -29,10 +29,6 @@
= icon('bell fw') = icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
= todos_pending_count = todos_pending_count
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled? - if Gitlab::Sherlock.enabled?
%li %li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions', = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
...@@ -48,6 +44,8 @@ ...@@ -48,6 +44,8 @@
= link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
%li %li
= link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" } = link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" }
%li
= link_to "Help", help_path, aria: { label: "Help" }
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" } = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" }
......
%ul.nav.nav-sidebar .nav-sidebar
.sidebar-header Across GitLab
%ul.nav
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
%span %span
Projects Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
%span
Todos
%span.count.js-todos-count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
%span %span
...@@ -39,11 +36,3 @@ ...@@ -39,11 +36,3 @@
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
%span %span
Snippets Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
%span
Help
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
%span
Profile Settings
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) } .project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
%div{ class: container_class } %div{ class: container_class }
= project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70 avatar-tile') .image-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title %h1.project-title
= @project.name = @project.name
%span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
.board-inner .board-inner
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
%span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')",
data: { container: "body", placement: "bottom" } }
{{ list.title }} {{ list.title }}
.board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" }
%span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" }
......
.content-block.build-header .content-block.build-header
.header-content
= ci_status_with_icon(@build.status) = ci_status_with_icon(@build.status)
Build Build
%strong ##{@build.id} %strong ##{@build.id}
...@@ -12,5 +13,7 @@ ...@@ -12,5 +13,7 @@
- if @build.user - if @build.user
= render "user" = render "user"
= time_ago_with_tooltip(@build.created_at) = time_ago_with_tooltip(@build.created_at)
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted pull-right', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left') = icon('angle-double-left')
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
.title .title
Build details Build details
- if can?(current_user, :update_build, @build) && @build.retryable? - if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
- if @build.merge_request - if @build.merge_request
%p.build-detail-row %p.build-detail-row
%span.build-light-text Merge Request: %span.build-light-text Merge Request:
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
= custom_icon('icon_fork') = custom_icon('icon_fork')
%span Fork %span Fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn' do
= custom_icon('icon_fork') = custom_icon('icon_fork')
%span Fork %span Fork
%div.count-with-arrow %div.count-with-arrow
%span.arrow %span.arrow
= link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count' do
= @project.forks_count = @project.forks_count
- if current_user - if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project) - if current_user.starred?(@project)
= icon('star') = icon('star')
%span.starred Unstar %span.starred Unstar
......
...@@ -118,6 +118,7 @@ ...@@ -118,6 +118,7 @@
Project avatar Project avatar
.form-group .form-group
- if @project.avatar? - if @project.avatar?
.image-container.s160
= project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160') = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
%p.light %p.light
- if @project.avatar_in_git - if @project.avatar_in_git
...@@ -180,6 +181,7 @@ ...@@ -180,6 +181,7 @@
%ul %ul
%li Build traces and artifacts %li Build traces and artifacts
%li LFS objects %li LFS objects
%li Container registry images
%hr %hr
- if can? current_user, :archive_project, @project - if can? current_user, :archive_project, @project
.row.prepend-top-default .row.prepend-top-default
......
...@@ -33,7 +33,12 @@ ...@@ -33,7 +33,12 @@
.form-actions .form-actions
- if @page && @page.persisted? - if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn" = f.submit 'Save changes', class: "btn-save btn"
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel" .pull-right
- if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do
Delete
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
- else - else
= f.submit 'Create page', class: "btn-create btn" = f.submit 'Create page', class: "btn-create btn"
.pull-right
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
...@@ -7,6 +7,3 @@ ...@@ -7,6 +7,3 @@
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
Edit Edit
- if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
Delete
...@@ -19,7 +19,5 @@ ...@@ -19,7 +19,5 @@
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New Page New Page
= render 'main_links'
= render 'form' = render 'form'
%ul.nav-links.event-filter.scrolling-tabs %ul.nav-links.event-filter.scrolling-tabs
= event_filter_link EventFilter.all, 'All' = event_filter_link EventFilter.all, 'All'
- if event_filter_visible(:repository)
= event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.push, 'Push events'
- if event_filter_visible(:merge_requests)
= event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.merged, 'Merge events'
- if event_filter_visible(:issues)
= event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team' = event_filter_link EventFilter.team, 'Team'
...@@ -9,11 +9,13 @@ ...@@ -9,11 +9,13 @@
= f.label :path, class: 'control-label' do = f.label :path, class: 'control-label' do
Group path Group path
.col-sm-10 .col-sm-10
.input-group .input-group.gl-field-error-anchor
.input-group-addon .input-group-addon
= root_url = root_url
= f.text_field :path, placeholder: 'open-source', class: 'form-control', = f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+",
required: true, title: 'Please choose a group name with no special characters.'
- if @group.persisted? - if @group.persisted?
.alert.alert-warning.prepend-top-10 .alert.alert-warning.prepend-top-10
%ul %ul
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false) = visibility_level_icon(group.visibility_level, fw: false)
.image-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs" = image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title .title
= link_to group, class: 'group-name' do = link_to group, class: 'group-name' do
......
- project = @target_project || @project - project = @target_project || @project
- extra_class = extra_class || '' - extra_class = extra_class || ''
- show_menu_above = show_menu_above || false - show_menu_above = show_menu_above || false
- selected_text = selected.try(:title) - selected_text = selected.try(:title) || params[:milestone_title]
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
- if selected.present? - if selected.present?
= hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project - if project
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
- if avatar - if avatar
.dash-project-avatar .dash-project-avatar
.image-container.s40
- if use_creator_avatar - if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
......
.clearfix .clearfix
- groups.each do |group| - groups.each do |group|
= link_to group, class: 'profile-groups-avatars inline', title: group.name do = link_to group, class: 'profile-groups-avatars inline', title: group.name do
.image-container.s40
= image_tag group_icon(group), class: 'avatar group-avatar s40' = image_tag group_icon(group), class: 'avatar group-avatar s40'
...@@ -16,7 +16,7 @@ class PostReceive ...@@ -16,7 +16,7 @@ class PostReceive
post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes) post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes)
if post_received.project.nil? if post_received.project.nil?
log("Triggered hook for non-existing project with full path \"#{repo_path} \"") log("Triggered hook for non-existing project with full path \"#{repo_path}\"")
return false return false
end end
...@@ -25,7 +25,7 @@ class PostReceive ...@@ -25,7 +25,7 @@ class PostReceive
elsif post_received.regular_project? elsif post_received.regular_project?
process_project_changes(post_received) process_project_changes(post_received)
else else
log("Triggered hook for unidentifiable repository type with full path \"#{repo_path} \"") log("Triggered hook for unidentifiable repository type with full path \"#{repo_path}\"")
false false
end end
end end
...@@ -37,7 +37,7 @@ class PostReceive ...@@ -37,7 +37,7 @@ class PostReceive
@user ||= post_received.identify(newrev) @user ||= post_received.identify(newrev)
unless @user unless @user
log("Triggered hook for non-existing user \"#{post_received.identifier} \"") log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
return false return false
end end
......
#!/usr/bin/env ruby
#
# Generate a changelog entry file in the correct location.
#
# Automatically stages the file and amends the previous commit if the `--amend`
# argument is used.
require 'optparse'
require 'yaml'
Options = Struct.new(
:amend,
:author,
:dry_run,
:force,
:merge_request,
:title
)
class ChangelogOptionParser
def self.parse(argv)
options = Options.new
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
# Note: We do not provide a shorthand for this in order to match the `git
# commit` interface
opts.on('--amend', 'Amend the previous commit') do |value|
options.amend = value
end
opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
options.force = value
end
opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
options.merge_request = value
end
opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
options.dry_run = value
end
opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
options.author = git_user_name if value
end
opts.on('-h', '--help', 'Print help message') do
$stdout.puts opts
exit
end
end
parser.parse!(argv)
# Title is everything that remains, but let's clean it up a bit
options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
options
end
def self.git_user_name
%x{git config user.name}.strip
end
end
class ChangelogEntry
attr_reader :options
def initialize(options)
@options = options
assert_feature_branch!
assert_new_file!
assert_title!
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
unless options.dry_run
write
amend_commit if options.amend
end
end
def contents
YAML.dump(
'title' => title,
'merge_request' => options.merge_request,
'author' => options.author
)
end
def write
File.write(file_path, contents)
end
def amend_commit
%x{git add #{file_path}}
exec("git commit --amend")
end
private
def fail_with(message)
$stderr.puts "\e[31merror\e[0m #{message}"
exit 1
end
def assert_feature_branch!
return unless branch_name == 'master'
fail_with "Create a branch first!"
end
def assert_new_file!
return unless File.exist?(file_path)
return if options.force
fail_with "#{file_path} already exists! Use `--force` to overwrite."
end
def assert_title!
return if options.title.length > 0 || options.amend
fail_with "Provide a title for the changelog entry or use `--amend`" \
" to use the title from the previous commit."
end
def title
if options.title.empty?
last_commit_subject
else
options.title
end
end
def last_commit_subject
%x{git log --format="%s" -1}.strip
end
def file_path
File.join(
unreleased_path,
branch_name.gsub(/[^\w-]/, '-') << '.yml'
)
end
def unreleased_path
File.join('changelogs', 'unreleased').tap do |path|
path << '-ee' if ee?
end
end
def ee?
@ee ||= File.exist?(File.expand_path('../CHANGELOG-EE.md', __dir__))
end
def branch_name
@branch_name ||= %x{git symbolic-ref HEAD}.strip.sub(%r{\Arefs/heads/}, '')
end
end
if $0 == __FILE__
options = ChangelogOptionParser.parse(ARGV)
ChangelogEntry.new(options)
end
# vim: ft=ruby
...@@ -432,7 +432,9 @@ production: &base ...@@ -432,7 +432,9 @@ production: &base
## Repositories settings ## Repositories settings
repositories: repositories:
# Paths where repositories can be stored. Give the canonicalized absolute pathname. # Paths where repositories can be stored. Give the canonicalized absolute pathname.
# NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! # IMPORTANT: None of the path components may be symlink, because
# gitlab-shell invokes Dir.pwd inside the repository path and that results
# real path not the symlink.
storages: # You must have at least a `default` storage path. storages: # You must have at least a `default` storage path.
default: /home/git/repositories/ default: /home/git/repositories/
......
# Post deployment migrations are included by default. This file must be loaded
# before other initializers as Rails may otherwise memoize a list of migrations
# excluding the post deployment migrations.
unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
path = Rails.root.join('db', 'post_migrate').to_s
Rails.application.config.paths['db/migrate'] << path
# Rails memoizes migrations at certain points where it won't read the above
# path just yet. As such we must also update the following list of paths.
ActiveRecord::Migrator.migrations_paths << path
end
...@@ -42,3 +42,19 @@ end ...@@ -42,3 +42,19 @@ end
Sidekiq.configure_client do |config| Sidekiq.configure_client do |config|
config.redis = redis_config_hash config.redis = redis_config_hash
end end
# The Sidekiq client API always adds the queue to the Sidekiq queue
# list, but mail_room and gitlab-shell do not. This is only necessary
# for monitoring.
config = YAML.load_file(Rails.root.join('config', 'sidekiq_queues.yml').to_s)
begin
Sidekiq.redis do |conn|
conn.pipelined do
config[:queues].each do |queue|
conn.sadd('queues', queue[0])
end
end
end
rescue Redis::BaseError, SocketError
end
# Maintenance Rake Tasks
## Gather information about GitLab and the system it runs on
This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues.
**Omnibus Installation**
```
sudo gitlab-rake gitlab:env:info
```
**Source Installation**
```
bundle exec rake gitlab:env:info RAILS_ENV=production
```
Example output:
```
System information
System: Debian 7.8
Current User: git
Using RVM: no
Ruby Version: 2.1.5p273
Gem Version: 2.4.3
Bundler Version: 1.7.6
Rake Version: 10.3.2
Sidekiq Version: 2.17.8
GitLab information
Version: 7.7.1
Revision: 41ab9e1
Directory: /home/git/gitlab
DB Adapter: postgresql
URL: https://gitlab.example.com
HTTP Clone URL: https://gitlab.example.com/some-project.git
SSH Clone URL: git@gitlab.example.com:some-project.git
Using LDAP: no
Using Omniauth: no
GitLab Shell
Version: 2.4.1
Repositories: /home/git/repositories/
Hooks: /home/git/gitlab-shell/hooks/
Git: /usr/bin/git
```
## Check GitLab configuration
Runs the following rake tasks:
- `gitlab:gitlab_shell:check`
- `gitlab:sidekiq:check`
- `gitlab:app:check`
It will check that each component was setup according to the installation guide and suggest fixes for issues found.
You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide).
**Omnibus Installation**
```
sudo gitlab-rake gitlab:check
```
**Source Installation**
```
bundle exec rake gitlab:check RAILS_ENV=production
```
NOTE: Use SANITIZE=true for gitlab:check if you want to omit project names from the output.
Example output:
```
Checking Environment ...
Git configured for git user? ... yes
Has python2? ... yes
python2 is supported version? ... yes
Checking Environment ... Finished
Checking GitLab Shell ...
GitLab Shell version? ... OK (1.2.0)
Repo base directory exists? ... yes
Repo base directory is a symlink? ... no
Repo base owned by git:git? ... yes
Repo base access is drwxrws---? ... yes
post-receive hook up-to-date? ... yes
post-receive hooks in repos are links: ... yes
Checking GitLab Shell ... Finished
Checking Sidekiq ...
Running? ... yes
Checking Sidekiq ... Finished
Checking GitLab ...
Database config exists? ... yes
Database is SQLite ... no
All migrations up? ... yes
GitLab config exists? ... yes
GitLab config outdated? ... no
Log directory writable? ... yes
Tmp directory writable? ... yes
Init script exists? ... yes
Init script up-to-date? ... yes
Redis version >= 2.0.0? ... yes
Checking GitLab ... Finished
```
## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file.
**Omnibus Installation**
```
sudo gitlab-rake gitlab:shell:setup
```
**Source Installation**
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
```
```
This will rebuild an authorized_keys file.
You will lose any data stored in authorized_keys file.
Do you want to continue (yes/no)? yes
```
## Clear redis cache
If for some reason the dashboard shows wrong information you might want to
clear Redis' cache.
**Omnibus Installation**
```
sudo gitlab-rake cache:clear
```
**Source Installation**
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
## Precompile the assets
Sometimes during version upgrades you might end up with some wrong CSS or
missing some icons. In that case, try to precompile the assets again.
Note that this only applies to source installations and does NOT apply to
Omnibus packages.
**Source Installation**
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```
For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
the release of upstream GitLab. The omnibus version includes optimized versions
of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
## Tracking Deployments
GitLab provides a Rake task that lets you track deployments in GitLab
Performance Monitoring. This Rake task simply stores the current GitLab version
in the GitLab Performance Monitoring database.
**Omnibus Installation**
```
sudo gitlab-rake gitlab:track_deployment
```
**Source Installation**
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
## Create or repair repository hooks symlink
If the GitLab shell hooks directory location changes or another circumstance
leads to the hooks symlink becoming missing or invalid, run this Rake task
to create or repair the symlinks.
**Omnibus Installation**
```
sudo gitlab-rake gitlab:shell:create_hooks
```
**Source Installation**
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
```
...@@ -107,7 +107,7 @@ downtime. Otherwise skip to the next section. ...@@ -107,7 +107,7 @@ downtime. Otherwise skip to the next section.
1. To see the current threads, run: 1. To see the current threads, run:
``` ```
apply all thread bt thread apply all bt
``` ```
1. Once you're done debugging with `gdb`, be sure to detach from the process and exit: 1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
......
...@@ -1139,6 +1139,7 @@ Parameters: ...@@ -1139,6 +1139,7 @@ Parameters:
| `pipeline_events` | boolean | no | Trigger hook on pipeline events | | `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events | | `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | | `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
### Edit project hook ### Edit project hook
...@@ -1164,6 +1165,7 @@ Parameters: ...@@ -1164,6 +1165,7 @@ Parameters:
| `pipeline_events` | boolean | no | Trigger hook on pipeline events | | `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events | | `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | | `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
### Delete project hook ### Delete project hook
......
...@@ -28,9 +28,12 @@ Example response: ...@@ -28,9 +28,12 @@ Example response:
```json ```json
[ [
{ {
"id" : 1, "id":1,
"url" : "https://gitlab.example.com/hook", "url":"https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z" "created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
"enable_ssl_verification":true
} }
] ]
``` ```
...@@ -48,6 +51,10 @@ POST /hooks ...@@ -48,6 +51,10 @@ POST /hooks
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `url` | string | yes | The hook URL | | `url` | string | yes | The hook URL |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
| `push_events` | boolean | no | When true, the hook will fire on push events |
| `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request: Example request:
...@@ -60,9 +67,12 @@ Example response: ...@@ -60,9 +67,12 @@ Example response:
```json ```json
[ [
{ {
"id" : 2, "id":1,
"url" : "https://gitlab.example.com/hook", "url":"https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z" "created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
"enable_ssl_verification":true
} }
] ]
``` ```
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
## Process ## Process
- [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed. - [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
- [Merge request performance guidelines](merge_request_performance_guidelines.md) - [Merge request performance guidelines](merge_request_performance_guidelines.md)
for ensuring merge requests do not negatively impact GitLab performance for ensuring merge requests do not negatively impact GitLab performance
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
- [What requires downtime?](what_requires_downtime.md) - [What requires downtime?](what_requires_downtime.md)
- [Adding database indexes](adding_database_indexes.md) - [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md)
## Compliance ## Compliance
......
# Generate a changelog entry
This guide contains instructions for generating a changelog entry data file, as
well as information and history about our changelog process.
## Overview
Each bullet point, or **entry**, in our [`CHANGELOG.md`][changelog.md] file is
generated from a single data file in the [`changelogs/unreleased/`][unreleased]
(or corresponding EE) folder. The file is expected to be a [YAML] file in the
following format:
```yaml
---
title: "Going through change[log]s"
merge_request: 1972
author: Ozzy Osbourne
```
The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community
contributors. Both are optional.
Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members should not.
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
[changelog.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG.md
[unreleased]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/changelogs/
[YAML]: https://en.wikipedia.org/wiki/YAML
## Instructions
A `bin/changelog` script is available to generate the changelog entry file
automatically.
Its simplest usage is to provide the value for `title`:
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
create changelogs/unreleased/my-feature.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
```
The entry filename is based on the name of the current Git branch. If you run
the command above on a branch called `feature/hey-dz`, it will generate a
`changelogs/unreleased/feature-hey-dz.yml` file.
### Arguments
| Argument | Shorthand | Purpose |
| ----------------- | --------- | --------------------------------------------- |
| `--amend` | | Amend the previous commit |
| `--force` | `-f` | Overwrite an existing entry |
| `--merge-request` | `-m` | Merge Request ID |
| `--dry-run` | `-n` | Don't actually write anything, just print |
| `--git-username` | `-u` | Use Git user.name configuration as the author |
| `--help` | `-h` | Print help message |
#### `--amend`
You can pass the **`--amend`** argument to automatically stage the generated
file and amend it to the previous commit.
If you use **`--amend`** and don't provide a title, it will automatically use
the "subject" of the previous commit, which is the first line of the commit
message:
```text
$ git show --oneline
ab88683 Added an awesome new feature to GitLab
$ bin/changelog --amend
create changelogs/unreleased/feature-hey-dz.yml
---
title: Added an awesome new feature to GitLab
merge_request:
author:
```
#### `--force` or `-f`
Use **`--force`** or **`-f`** to overwrite an existing changelog entry if it
already exists.
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
error changelogs/unreleased/feature-hey-dz.yml already exists! Use `--force` to overwrite.
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' --force
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
```
#### `--merge-request` or `-m`
Use the **`--merge-request`** or **`-m`** argument to provide the
`merge_request` value:
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -m 1983
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
```
#### `--dry-run` or `-n`
Use the **`--dry-run`** or **`-n`** argument to prevent actually writing or
committing anything:
```text
$ bin/changelog --amend --dry-run
create changelogs/unreleased/feature-hey-dz.yml
---
title: Added an awesome new feature to GitLab
merge_request:
author:
$ ls changelogs/unreleased/
```
#### `--git-username` or `-u`
Use the **`--git-username`** or **`-u`** argument to automatically fill in the
`author` value with your configured Git `user.name` value:
```text
$ git config user.name
Jane Doe
$ bin/changelog --u 'Hey DZ, I added a feature to GitLab!'
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request:
author: Jane Doe
```
## History and Reasoning
Our `CHANGELOG` file was previously updated manually by each contributor that
felt their change warranted an entry. When two merge requests added their own
entries at the same spot in the list, it created a merge conflict in one as soon
as the other was merged. When we had dozens of merge requests fighting for the
same changelog entry location, this quickly became a major source of merge
conflicts and delays in development.
This led us to a [boring solution] of "add your entry in a random location in
the list." This actually worked pretty well as we got further along in each
monthly release cycle, but at the start of a new cycle, when a new version
section was added and there were fewer places to "randomly" add an entry, the
conflicts became a problem again until we had a sufficient number of entries.
On top of all this, it created an entirely different headache for [release managers]
when they cherry-picked a commit into a stable branch for a patch release. If
the commit included an entry in the `CHANGELOG`, it would include the entire
changelog for the latest version in `master`, so the release manager would have
to manually remove the later entries. They often would have had to do this
multiple times per patch release. This was compounded when we had to release
multiple patches at once due to a security issue.
We needed to automate all of this manual work. So we [started brainstorming].
After much discussion we settled on the current solution of one file per entry,
and then compiling the entries into the overall `CHANGELOG.md` file during the
[release process].
[boring solution]: https://about.gitlab.com/handbook/#boring-solutions
[release managers]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/release-manager.md
[started brainstorming]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17826
[release process]: https://gitlab.com/gitlab-org/release-tools
---
[Return to Development documentation](README.md)
# Post Deployment Migrations
Post deployment migrations are regular Rails migrations that can optionally be
executed after a deployment. By default these migrations are executed alongside
the other migrations. To skip these migrations you will have to set the
environment variable `SKIP_POST_DEPLOYMENT_MIGRATIONS` to a non-empty value
when running `rake db:migrate`.
For example, this would run all migrations including any post deployment
migrations:
```bash
bundle exec rake db:migrate
```
This however will skip post deployment migrations:
```bash
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bundle exec rake db:migrate
```
## Deployment Integration
Say you're using Chef for deploying new versions of GitLab and you'd like to run
post deployment migrations after deploying a new version. Let's assume you
normally use the command `chef-client` to do so. To make use of this feature
you'd have to run this command as follows:
```bash
SKIP_POST_DEPLOYMENT_MIGRATIONS=true sudo chef-client
```
Once all servers have been updated you can run `chef-client` again on a single
server _without_ the environment variable.
The process is similar for other deployment techniques: first you would deploy
with the environment variable set, then you'll essentially re-deploy a single
server but with the variable _unset_.
## Creating Migrations
To create a post deployment migration you can use the following Rails generator:
```bash
bundle exec rails g post_deployment_migration migration_name_here
```
This will generate the migration file in `db/post_migrate`. These migrations
behave exactly like regular Rails migrations.
## Use Cases
Post deployment migrations can be used to perform migrations that mutate state
that an existing version of GitLab depends on. For example, say you want to
remove a column from a table. This requires downtime as a GitLab instance
depends on this column being present while it's running. Normally you'd follow
these steps in such a case:
1. Stop the GitLab instance
2. Run the migration removing the column
3. Start the GitLab instance again
Using post deployment migrations we can instead follow these steps:
1. Deploy a new version of GitLab while ignoring post deployment migrations
2. Re-run `rake db:migrate` but without the environment variable set
Here we don't need any downtime as the migration takes place _after_ a new
version (which doesn't depend on the column anymore) has been deployed.
Some other examples where these migrations are useful:
* Cleaning up data generated due to a bug in GitLab
* Removing tables
* Migrating jobs from one Sidekiq queue to another
...@@ -132,6 +132,42 @@ Adding new Spinach scenarios is acceptable _only if_ the new scenario requires ...@@ -132,6 +132,42 @@ Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
no more than one new `step` definition. If more than that is required, the no more than one new `step` definition. If more than that is required, the
test should be re-implemented using RSpec instead. test should be re-implemented using RSpec instead.
## Testing Rake Tasks
To make testing Rake tasks a little easier, there is a helper that can be included
in lieu of the standard Spec helper. Instead of `require 'spec_helper'`, use
`require 'rake_helper'`. The helper includes `spec_helper` for you, and configures
a few other things to make testing Rake tasks easier.
At a minimum, requiring the Rake helper will redirect `stdout`, include the
runtime task helpers, and include the `RakeHelpers` Spec support module.
The `RakeHelpers` module exposes a `run_rake_task(<task>)` method to make
executing tasks simple. See `spec/support/rake_helpers.rb` for all available
methods.
Example:
```ruby
require 'rake_helper'
describe 'gitlab:shell rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/shell'
stub_warn_user_is_not_gitlab
end
describe 'install task' do
it 'invokes create_hooks task' do
expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
run_rake_task('gitlab:shell:install')
end
end
end
```
--- ---
[Return to Development documentation](README.md) [Return to Development documentation](README.md)
...@@ -271,9 +271,9 @@ sudo usermod -aG redis git ...@@ -271,9 +271,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-13-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-14-stable gitlab
**Note:** You can change `8-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-14-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
...@@ -135,7 +135,7 @@ password as they will be needed when configuring GitLab in the next section. ...@@ -135,7 +135,7 @@ password as they will be needed when configuring GitLab in the next section.
JIRA configuration in GitLab is done via a project's **Services**. JIRA configuration in GitLab is done via a project's **Services**.
#### GitLab 13.0 with JIRA v1000.x #### GitLab 8.13.0 with JIRA v1000.x
To enable JIRA integration in a project, navigate to the project's To enable JIRA integration in a project, navigate to the project's
and open the context menu clicking on the top right gear icon, then go to and open the context menu clicking on the top right gear icon, then go to
...@@ -160,7 +160,7 @@ with the linked JIRA project. ...@@ -160,7 +160,7 @@ with the linked JIRA project.
#### GitLab 6.x-7.7 with JIRA v6.x #### GitLab 6.x-7.7 with JIRA v6.x
_**Note:** GitLab versions 13.0 and up contain various integration improvements. _**Note:** GitLab versions 8.13.0 and up contain various integration improvements.
We strongly recommend upgrading._ We strongly recommend upgrading._
In `gitlab.yml` enable the JIRA issue tracker section by In `gitlab.yml` enable the JIRA issue tracker section by
......
...@@ -268,13 +268,20 @@ message `Can't verify CSRF token authenticity`. This means that there is an erro ...@@ -268,13 +268,20 @@ message `Can't verify CSRF token authenticity`. This means that there is an erro
the SAML request, but this error never reaches GitLab due to the CSRF check. the SAML request, but this error never reaches GitLab due to the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the To bypass this you can add `skip_before_action :verify_authenticity_token` to the
`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab, `omniauth_callbacks_controller.rb` file immediately after the `class` line and
where it can then be seen in the usual logs, or as a flash message in the login comment out the `protect_from_forgery` line using a `#` then restart Unicorn. This
screen. will allow the error to hit GitLab, where it can then be seen in the usual logs,
or as a flash message on the login screen.
That file is located at `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
for Omnibus installations and by default on `/home/git/gitlab/app/controllers` for That file is located in `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
installations from source. for Omnibus installations and by default in `/home/git/gitlab/app/controllers` for
installations from source. Restart Unicorn using the `sudo gitlab-ctl restart unicorn`
command on Omnibus installations and `sudo service gitlab restart` on installations
from source.
You may also find the [SSO Tracer](https://addons.mozilla.org/en-US/firefox/addon/sso-tracer)
(Firefox) and [SAML Chrome Panel](https://chrome.google.com/webstore/detail/saml-chrome-panel/paijfdbeoenhembfhkhllainmocckace)
(Chrome) browser extensions useful in your debugging.
### Invalid audience ### Invalid audience
......
# Maintenance # Maintenance Rake Tasks
## Gather information about GitLab and the system it runs on This document was moved to [administration/raketasks/maintenance](../administration/raketasks/maintenance.md).
This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues.
```
# omnibus-gitlab
sudo gitlab-rake gitlab:env:info
# installation from source
bundle exec rake gitlab:env:info RAILS_ENV=production
```
Example output:
```
System information
System: Debian 7.8
Current User: git
Using RVM: no
Ruby Version: 2.1.5p273
Gem Version: 2.4.3
Bundler Version: 1.7.6
Rake Version: 10.3.2
Sidekiq Version: 2.17.8
GitLab information
Version: 7.7.1
Revision: 41ab9e1
Directory: /home/git/gitlab
DB Adapter: postgresql
URL: https://gitlab.example.com
HTTP Clone URL: https://gitlab.example.com/some-project.git
SSH Clone URL: git@gitlab.example.com:some-project.git
Using LDAP: no
Using Omniauth: no
GitLab Shell
Version: 2.4.1
Repositories: /home/git/repositories/
Hooks: /home/git/gitlab-shell/hooks/
Git: /usr/bin/git
```
## Check GitLab configuration
Runs the following rake tasks:
- `gitlab:gitlab_shell:check`
- `gitlab:sidekiq:check`
- `gitlab:app:check`
It will check that each component was setup according to the installation guide and suggest fixes for issues found.
You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide).
```
# omnibus-gitlab
sudo gitlab-rake gitlab:check
# installation from source
bundle exec rake gitlab:check RAILS_ENV=production
```
NOTE: Use SANITIZE=true for gitlab:check if you want to omit project names from the output.
Example output:
```
Checking Environment ...
Git configured for git user? ... yes
Has python2? ... yes
python2 is supported version? ... yes
Checking Environment ... Finished
Checking GitLab Shell ...
GitLab Shell version? ... OK (1.2.0)
Repo base directory exists? ... yes
Repo base directory is a symlink? ... no
Repo base owned by git:git? ... yes
Repo base access is drwxrws---? ... yes
post-receive hook up-to-date? ... yes
post-receive hooks in repos are links: ... yes
Checking GitLab Shell ... Finished
Checking Sidekiq ...
Running? ... yes
Checking Sidekiq ... Finished
Checking GitLab ...
Database config exists? ... yes
Database is SQLite ... no
All migrations up? ... yes
GitLab config exists? ... yes
GitLab config outdated? ... no
Log directory writable? ... yes
Tmp directory writable? ... yes
Init script exists? ... yes
Init script up-to-date? ... yes
Redis version >= 2.0.0? ... yes
Checking GitLab ... Finished
```
## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:shell:setup
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
```
```
This will rebuild an authorized_keys file.
You will lose any data stored in authorized_keys file.
Do you want to continue (yes/no)? yes
```
## Clear redis cache
If for some reason the dashboard shows wrong information you might want to
clear Redis' cache.
For Omnibus-packages:
```
sudo gitlab-rake cache:clear
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
## Precompile the assets
Sometimes during version upgrades you might end up with some wrong CSS or
missing some icons. In that case, try to precompile the assets again.
Note that this only applies to source installations and does NOT apply to
omnibus packages.
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```
For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
the release of upstream GitLab. The omnibus version includes optimized versions
of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
## Tracking Deployments
GitLab provides a Rake task that lets you track deployments in GitLab
Performance Monitoring. This Rake task simply stores the current GitLab version
in the GitLab Performance Monitoring database.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:track_deployment
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
# From 8.13 to 8.14
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
guide links by version.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Update Ruby
We will continue supporting Ruby < 2.3 for the time being but we recommend you
upgrade to Ruby 2.3 if you're running a source installation, as this is the same
version that ships with our Omnibus package.
You can check which version you are running with `ruby -v`.
Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure --disable-install-rdoc
make
sudo make install
```
Install Bundler:
```bash
sudo gem install bundler --no-ri --no-rdoc
```
### 4. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-14-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-14-stable-ee
```
### 5. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v4.0.0
```
### 6. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.8.5
sudo -u git -H make
```
### 7. 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 postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
# 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
```
### 8. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-13-stable:config/gitlab.yml.example origin/8-14-stable:config/gitlab.yml.example
```
#### Git configuration
Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
the GitLab server during `git gc`.
```sh
sudo -u git -H git config --global repack.writeBitmaps true
```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-14-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-14-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/lib/support/init.d/gitlab.default.example#L38
#### SMTP configuration
If you're installing from source and use SMTP to deliver mail, you will need to add the following line
to config/initializers/smtp_settings.rb:
```ruby
ActionMailer::Base.delivery_method = :smtp
```
See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
sudo service gitlab start
sudo service nginx restart
### 10. 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:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.13)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.12 to 8.13](8.12-to-8.13.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.
...@@ -85,6 +85,8 @@ possible. ...@@ -85,6 +85,8 @@ possible.
- [MySQL installation guide](../install/database_mysql.md) contains additional - [MySQL installation guide](../install/database_mysql.md) contains additional
information about configuring GitLab to work with a MySQL database. information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md) - [Restoring from backup after a failed upgrade](restore_after_failure.md)
- [Upgrading PostgreSQL Using Slony](upgrading_postgresql_using_slony.md), for
upgrading a PostgreSQL database with minimal downtime.
[omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html [omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html
[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update [source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
......
This diff is collapsed.
...@@ -18,7 +18,7 @@ Feature: Dashboard Active Tab ...@@ -18,7 +18,7 @@ Feature: Dashboard Active Tab
Then the active main tab should be Merge Requests Then the active main tab should be Merge Requests
And no other main tabs should be active And no other main tabs should be active
Scenario: On Dashboard Help Scenario: On Dashboard Groups
Given I visit dashboard help page Given I visit dashboard groups page
Then the active main tab should be Help Then the active main tab should be Groups
And no other main tabs should be active And no other main tabs should be active
...@@ -11,7 +11,6 @@ Feature: Dashboard ...@@ -11,7 +11,6 @@ Feature: Dashboard
And I visit dashboard page And I visit dashboard page
Scenario: I should see projects list Scenario: I should see projects list
Then I should see "New Project" link
Then I should see "Shop" project link Then I should see "Shop" project link
Then I should see "Shop" project CI status Then I should see "Shop" project CI status
......
...@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps ...@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end end
step 'I visit the "Rake Tasks" help page' do step 'I visit the "Rake Tasks" help page' do
visit help_page_path("raketasks/maintenance") visit help_page_path("administration/raketasks/maintenance")
end end
step 'I should see "Rake Tasks" page markdown rendered' do step 'I should see "Rake Tasks" page markdown rendered' do
......
module SharedSidebarActiveTab module SharedSidebarActiveTab
include Spinach::DSL include Spinach::DSL
step 'the active main tab should be Help' do
ensure_active_main_tab('Help')
end
step 'no other main tabs should be active' do step 'no other main tabs should be active' do
expect(page).to have_selector('.nav-sidebar > li.active', count: 1) expect(page).to have_selector('.nav-sidebar li.active', count: 1)
end end
def ensure_active_main_tab(content) def ensure_active_main_tab(content)
...@@ -17,6 +13,10 @@ module SharedSidebarActiveTab ...@@ -17,6 +13,10 @@ module SharedSidebarActiveTab
ensure_active_main_tab('Projects') ensure_active_main_tab('Projects')
end end
step 'the active main tab should be Groups' do
ensure_active_main_tab('Groups')
end
step 'the active main tab should be Projects' do step 'the active main tab should be Projects' do
ensure_active_main_tab('Projects') ensure_active_main_tab('Projects')
end end
...@@ -28,8 +28,4 @@ module SharedSidebarActiveTab ...@@ -28,8 +28,4 @@ module SharedSidebarActiveTab
step 'the active main tab should be Merge Requests' do step 'the active main tab should be Merge Requests' do
ensure_active_main_tab('Merge Requests') ensure_active_main_tab('Merge Requests')
end end
step 'the active main tab should be Help' do
ensure_active_main_tab('Help')
end
end end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class <%= migration_class_name %> < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
end
end
...@@ -43,14 +43,13 @@ module API ...@@ -43,14 +43,13 @@ module API
end end
class Hook < Grape::Entity class Hook < Grape::Entity
expose :id, :url, :created_at expose :id, :url, :created_at, :push_events, :tag_push_events
expose :enable_ssl_verification
end end
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :push_events expose :project_id, :issues_events, :merge_requests_events
expose :issues_events, :merge_requests_events, :tag_push_events
expose :note_events, :build_events, :pipeline_events, :wiki_page_events expose :note_events, :build_events, :pipeline_events, :wiki_page_events
expose :enable_ssl_verification
end end
class BasicProjectDetails < Grape::Entity class BasicProjectDetails < Grape::Entity
......
...@@ -47,7 +47,8 @@ module API ...@@ -47,7 +47,8 @@ module API
:build_events, :build_events,
:pipeline_events, :pipeline_events,
:wiki_page_events, :wiki_page_events,
:enable_ssl_verification :enable_ssl_verification,
:token
] ]
@hook = user_project.hooks.new(attrs) @hook = user_project.hooks.new(attrs)
...@@ -82,7 +83,8 @@ module API ...@@ -82,7 +83,8 @@ module API
:build_events, :build_events,
:pipeline_events, :pipeline_events,
:wiki_page_events, :wiki_page_events,
:enable_ssl_verification :enable_ssl_verification,
:token
] ]
if @hook.update_attributes attrs if @hook.update_attributes attrs
......
...@@ -12,6 +12,7 @@ module API ...@@ -12,6 +12,7 @@ module API
end end
get do get do
hooks = SystemHook.all hooks = SystemHook.all
present hooks, with: Entities::Hook present hooks, with: Entities::Hook
end end
...@@ -19,10 +20,14 @@ module API ...@@ -19,10 +20,14 @@ module API
success Entities::Hook success Entities::Hook
end end
params do params do
requires :url, type: String, desc: 'The URL for the system hook' requires :url, type: String, desc: "The URL to send the request to"
optional :token, type: String, desc: 'The token used to validate payloads'
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end end
post do post do
hook = SystemHook.new declared(params).to_h hook = SystemHook.new declared(params, include_missing: false).to_h
if hook.save if hook.save
present hook, with: Entities::Hook present hook, with: Entities::Hook
......
require 'rails/generators'
module Rails
class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
def create_migration_file
timestamp = Time.now.strftime('%Y%m%d%H%I%S')
template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
end
def migration_class_name
file_name.camelize
end
end
end
...@@ -3,10 +3,25 @@ module Gitlab ...@@ -3,10 +3,25 @@ module Gitlab
class AttributeCleaner class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id'] ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
def self.clean!(relation_hash:) def self.clean(*args)
relation_hash.reject! do |key, _value| new(*args).clean
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key) end
def initialize(relation_hash:, relation_class:)
@relation_hash = relation_hash
@relation_class = relation_class
end
def clean
@relation_hash.reject do |key, _value|
prohibited_key?(key) || !@relation_class.attribute_method?(key)
end.except('id')
end end
private
def prohibited_key?(key)
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
end end
end end
end end
......
...@@ -43,6 +43,14 @@ module Gitlab ...@@ -43,6 +43,14 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
remove_symlinks!
end
def remove_symlinks!
Dir["#{@shared.export_path}/**/*"].each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true true
end end
end end
......
...@@ -55,7 +55,12 @@ module Gitlab ...@@ -55,7 +55,12 @@ module Gitlab
end end
def member_hash(member) def member_hash(member)
member.except('id').merge(source_id: @project.id, importing: true) parsed_hash(member).merge('source_id' => @project.id, 'importing' => true)
end
def parsed_hash(member)
Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: member.deep_stringify_keys,
relation_class: ProjectMember)
end end
def find_project_user_query(member) def find_project_user_query(member)
......
...@@ -9,8 +9,14 @@ module Gitlab ...@@ -9,8 +9,14 @@ module Gitlab
end end
def restore def restore
begin
json = IO.read(@path) json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json) @tree_hash = ActiveSupport::JSON.decode(json)
rescue => e
Rails.logger.error("Import/Export error: #{e.message}")
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
end
@project_members = @tree_hash.delete('project_members') @project_members = @tree_hash.delete('project_members')
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment