Commit f16dca30 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into pipeline-notifications

* upstream/master: (23 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
  Update docs and test description
  Update docs and unexpose token
  Initialize form validation on new group form.
  Unchange username_validator.
  Move snake_case to camelCase.
  Change show-gl-field-errors to gl-show-field-errors
  Fix changelog.
  List gl_field_error as gl_field_errors dep.
  Break out GlFieldError into separate file.
  Add gl field errors to group name edit form.
  ...
parents 17a6f942 3ac3106e
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)
- 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
- Adds user project membership expired event to clarify why user was removed (Callum Dryden)
......@@ -54,16 +57,16 @@ Please view this file on the master branch, on stable branches it's out of date.
- Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- 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)
- 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)
## 8.13.3
## 8.13.3 (2016-11-02)
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
- Fix project features default values
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
## 8.13.2 (2016-10-31)
......@@ -253,6 +256,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix broken Project API docs (Takuya Noguchi)
- 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
- Prevent running `GfmAutocomplete` setup for each diff note. !6569
......@@ -512,6 +520,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
- 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
- Don't send Private-Token (API authentication) headers to Sentry
......@@ -752,6 +764,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
- 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
- Don't send Private-Token (API authentication) headers to Sentry
......
......@@ -299,7 +299,7 @@
};
Dispatcher.prototype.initFieldErrors = function() {
$('.show-gl-field-errors').each((i, form) => {
$('.gl-show-field-errors').each((i, 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 */
((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();
}
}
//= require gl_field_error
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() {
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();
}
}
const customValidationFlag = 'no-gl-field-errors';
((global) => {
const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors {
constructor(form) {
......@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray()
.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);
}
......
......@@ -136,3 +136,35 @@ label {
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;
}
}
......@@ -75,43 +75,17 @@
.login-body {
font-size: 13px;
input + p {
margin-top: 5px;
}
.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;
}
}
.username .validation-success,
.gl-field-success-message {
.username .validation-success {
color: $green-normal;
}
.username .validation-error,
.gl-field-error-message {
.username .validation-error {
color: $red-normal;
}
.gl-field-hint {
color: $gl-text-color;
}
}
}
......
= render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box
%form.show-gl-field-errors
%form.gl-show-field-errors
.form-group
= label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
......
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box
.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_error_messages!
.form-group
......
= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
.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_error_messages!
= f.hidden_field :reset_password_token
......
= render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box
.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_error_messages!
.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
= 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."
......
= 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
= label_tag :username, 'Username or email'
= 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
= label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
......
......@@ -7,7 +7,7 @@
.login-box
.login-body
- 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
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
......
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.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_error_messages!
%div.form-group
......@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group
= 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-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
......
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box
.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_error_messages!
.form-group.append-bottom-20
......
......@@ -2,7 +2,7 @@
.panel-heading
Group settings
.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)
= render 'shared/group_form', f: f
......
......@@ -5,7 +5,7 @@
New Group
%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)
= render 'shared/group_form', f: f, autofocus: true
......
......@@ -9,11 +9,13 @@
= f.label :path, class: 'control-label' do
Group path
.col-sm-10
.input-group
.input-group.gl-field-error-anchor
.input-group-addon
= root_url
= 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?
.alert.alert-warning.prepend-top-10
%ul
......
......@@ -22,7 +22,7 @@ class ChangelogOptionParser
options = Options.new
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{__FILE__} [options]"
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
......
......@@ -27,11 +27,14 @@ Example response:
```json
[
{
"id" : 1,
"url" : "https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z"
}
{
"id":1,
"url":"https://gitlab.example.com/hook",
"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
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `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:
......@@ -59,11 +66,14 @@ Example response:
```json
[
{
"id" : 2,
"url" : "https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z"
}
{
"id":1,
"url":"https://gitlab.example.com/hook",
"created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
"enable_ssl_verification":true
}
]
```
......
......@@ -21,6 +21,9 @@ 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.
......
......@@ -271,9 +271,9 @@ sudo usermod -aG redis git
### Clone the Source
# 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
......
# 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.
......@@ -43,14 +43,13 @@ module API
end
class Hook < Grape::Entity
expose :id, :url, :created_at
expose :id, :url, :created_at, :push_events, :tag_push_events
expose :enable_ssl_verification
end
class ProjectHook < Hook
expose :project_id, :push_events
expose :issues_events, :merge_requests_events, :tag_push_events
expose :project_id, :issues_events, :merge_requests_events
expose :note_events, :build_events, :pipeline_events, :wiki_page_events
expose :enable_ssl_verification
end
class BasicProjectDetails < Grape::Entity
......
......@@ -12,6 +12,7 @@ module API
end
get do
hooks = SystemHook.all
present hooks, with: Entities::Hook
end
......@@ -19,10 +20,14 @@ module API
success Entities::Hook
end
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
post do
hook = SystemHook.new declared(params).to_h
hook = SystemHook.new declared(params, include_missing: false).to_h
if hook.save
present hook, with: Entities::Hook
......
......@@ -3,10 +3,25 @@ module Gitlab
class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
def self.clean!(relation_hash:)
relation_hash.reject! do |key, _value|
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
end
def self.clean(*args)
new(*args).clean
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
private
def prohibited_key?(key)
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
end
end
end
......
......@@ -43,6 +43,14 @@ module Gitlab
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
end
end
......
......@@ -55,7 +55,12 @@ module Gitlab
end
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
def find_project_user_query(member)
......
......@@ -9,8 +9,14 @@ module Gitlab
end
def restore
json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json)
begin
json = IO.read(@path)
@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')
ActiveRecord::Base.no_touching do
......
......@@ -14,7 +14,7 @@ module Gitlab
priorities: :label_priorities,
label: :project_label }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze
......@@ -30,7 +30,7 @@ module Gitlab
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:)
@relation_name = OVERRIDES[relation_sym] || relation_sym
@relation_hash = relation_hash.except('id', 'noteable_id').merge('project_id' => project_id)
@relation_hash = relation_hash.except('noteable_id').merge('project_id' => project_id)
@members_mapper = members_mapper
@user = user
@imported_object_retries = 0
......@@ -172,11 +172,8 @@ module Gitlab
end
def parsed_relation_hash
@parsed_relation_hash ||= begin
Gitlab::ImportExport::AttributeCleaner.clean!(relation_hash: @relation_hash)
@relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
end
@parsed_relation_hash ||= Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: @relation_hash,
relation_class: relation_class)
end
def set_st_diffs
......
......@@ -24,12 +24,19 @@ module Gitlab
end
def verify_version!(version)
if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
if different_version?(version)
raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
else
true
end
end
def different_version?(version)
Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
rescue => e
Rails.logger.error("Import/Export error: #{e.message}")
raise Gitlab::ImportExport::Error.new('Incorrect VERSION format')
end
end
end
end
%form.show-gl-field-errors{action: 'submit', method: 'post'}
%form.gl-show-field-errors{action: 'submit', method: 'post'}
.form-group
%input.required-text{required: true, type: 'text'} Text
.form-group
......@@ -10,6 +10,6 @@
.form-group
%input.hidden{ type:'hidden' }
.form-group
%input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate
%input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
.form-group
%input.submit{type: 'submit'} Submit
......@@ -8,7 +8,7 @@
describe('GL Style Field Errors', function() {
beforeEach(function() {
fixture.load('gl_field_errors.html');
const $form = this.$form = $('form.show-gl-field-errors');
const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form);
});
......@@ -21,7 +21,7 @@
});
it('should ignore elements with custom error handling', function() {
const customErrorFlag = 'no-gl-field-errors';
const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`);
expect(customErrorElem.length).toBe(1);
......
require 'spec_helper'
describe Gitlab::ImportExport::AttributeCleaner, lib: true do
let(:relation_class){ double('relation_class').as_null_object }
let(:unsafe_hash) do
{
'id' => 101,
'service_id' => 99,
'moved_to_id' => 99,
'namespace_id' => 99,
......@@ -27,8 +29,9 @@ describe Gitlab::ImportExport::AttributeCleaner, lib: true do
end
it 'removes unwanted attributes from the hash' do
described_class.clean!(relation_hash: unsafe_hash)
# allow(relation_class).to receive(:attribute_method?).and_return(true)
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
expect(unsafe_hash).to eq(post_safe_hash)
expect(parsed_hash).to eq(post_safe_hash)
end
end
require 'spec_helper'
describe Gitlab::ImportExport::FileImporter, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir::tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
setup_files
described_class.import(archive_file: '', shared: shared)
end
after do
FileUtils.rm_rf(export_path)
end
it 'removes symlinks in root folder' do
expect(File.exist?(symlink_file)).to be false
end
it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false
end
it 'does not remove a valid file' do
expect(File.exist?(valid_file)).to be true
end
def setup_files
FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
FileUtils.touch(valid_file)
FileUtils.ln_s(valid_file, symlink_file)
FileUtils.ln_s(valid_file, subfolder_symlink_file)
end
end
require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
......@@ -175,6 +176,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1)
end
end
context 'project.json file access check' do
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'project.json')
allow(shared).to receive(:export_path).and_call_original
restored_project_json
expect(shared.errors.first).not_to include('test')
end
end
end
end
end
end
require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::VersionChecker, services: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
describe 'bundle a project Git repo' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
let(:version) { Gitlab::ImportExport.version }
before do
......@@ -27,4 +29,16 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
end
end
end
describe 'version file access check' do
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'VERSION')
described_class.check!(shared: shared)
expect(shared.errors.first).not_to include('test')
end
end
end
end
require 'spec_helper'
include Gitlab::Routing.url_helpers
describe JiraService, models: true do
include Gitlab::Routing.url_helpers
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
......@@ -79,7 +80,9 @@ describe JiraService, models: true do
stub_config_setting(relative_url_root: '/gitlab')
stub_config_setting(url: Settings.send(:build_gitlab_url))
Project.default_url_options[:script_name] = "/gitlab"
allow(JiraService).to receive(:default_url_options) do
{ script_name: '/gitlab' }
end
@jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
......
......@@ -13,6 +13,7 @@ describe API::API, api: true do
context "when no user" do
it "returns authentication error" do
get api("/hooks")
expect(response).to have_http_status(401)
end
end
......@@ -20,6 +21,7 @@ describe API::API, api: true do
context "when not an admin" do
it "returns forbidden error" do
get api("/hooks", user)
expect(response).to have_http_status(403)
end
end
......@@ -27,9 +29,12 @@ describe API::API, api: true do
context "when authenticated as admin" do
it "returns an array of hooks" do
get api("/hooks", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be true
expect(json_response.first['tag_push_events']).to be false
end
end
end
......@@ -43,6 +48,7 @@ describe API::API, api: true do
it "responds with 400 if url not given" do
post api("/hooks", admin)
expect(response).to have_http_status(400)
end
......@@ -51,6 +57,14 @@ describe API::API, api: true do
post api("/hooks", admin)
end.not_to change { SystemHook.count }
end
it 'sets default values for events' do
post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
expect(response).to have_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
expect(json_response['tag_push_events']).to be false
end
end
describe "GET /hooks/:id" do
......
module ImportExport
module CommonUtil
def setup_symlink(tmpdir, symlink_name)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(tmpdir)
File.open("#{tmpdir}/test", 'w') { |file| file.write("test") }
FileUtils.ln_s("#{tmpdir}/test", "#{tmpdir}/#{symlink_name}")
end
end
end
......@@ -39,3 +39,6 @@ captures/
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
......@@ -7,6 +7,11 @@
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
......@@ -34,3 +39,13 @@
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf
.architect
bootstrap.css
bootstrap.js
bootstrap.json
bootstrap.jsonp
build/
classic.json
classic.jsonp
ext/
modern.json
modern.jsonp
resources/sass/.sass-cache/
......@@ -4,9 +4,6 @@
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
......
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
This diff is collapsed.
......@@ -7,6 +7,7 @@ app/storage/
# Laravel 5 & Lumen specific
bootstrap/cache/
public/storage
.env.*.php
.env.php
.env
......
# For projects using nanoc (http://nanoc.ws/)
# For projects using Nanoc (http://nanoc.ws/)
# Default location for output, needs to match output_dir's value found in config.yaml
# Default location for output (needs to match output_dir's value found in nanoc.yaml)
output/
# Temporary file directory
......
......@@ -11,3 +11,10 @@ system/cache/
system/logs/
system/storage/
# vQmod log files
vqmod/logs/*
# vQmod cache files
vqmod/vqcache/*
vqmod/checked.cache
vqmod/mods.cache
......@@ -66,7 +66,7 @@ docs/_build/
# PyBuilder
target/
# IPython Notebook
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
......
......@@ -5,3 +5,6 @@
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
......@@ -61,6 +61,15 @@ acs-*.bib
# fixme
*.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
*.[1-9]
*.[1-9][0-9]
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
......
# Visual Studio 2015 user specific files
.vs/
# Visual Studio 2015 database file
*.VC.db
# Compiled Object files
*.slo
*.lo
......
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
......@@ -44,6 +47,7 @@ dlldata.c
project.lock.json
project.fragment.lock.json
artifacts/
Properties/launchSettings.json
*_i.c
*_p.c
......@@ -238,6 +242,9 @@ FakesAssemblies/
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
......
# Based on openjdk:8, already includes lein
image: clojure:lein-2.7.0
# If you need to configure a database, add a `services` section here
# See https://docs.gitlab.com/ce/ci/services/postgres.html
# Make sure you configure the connection as well
before_script:
# If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
#
#- apt-get update -y
#
# Retrieve project dependencies
# Do this on before_script since it'll be shared between both test and
# any production sections a user adds
- lein deps
test:
script:
# If you need to run any migrations or configure the database, this
# would be the point to do it.
- lein test
# This file is a template, and might need editing before it works on your project.
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/crystallang/crystal/
image: "crystallang/crystal:latest"
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
# services:
# - mysql:latest
# - redis:latest
# - postgres:latest
# variables:
# POSTGRES_DB: database_name
# Cache shards in between builds
cache:
paths:
- libs
# This is a basic example for a shard or script which doesn't use
# services such as redis or postgres
before_script:
- apt-get update -qq && apt-get install -y -qq libxml2-dev
- crystal -v # Print out Crystal version for debugging
- shards
# If you are using built-in Crystal Spec.
spec:
script:
- crystal spec
# If you are using minitest.cr
minitest:
script:
- crystal test/spec_test.cr # change to the file(s) you execute for tests
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