Commit 50c66197 authored by Stan Hu's avatar Stan Hu

Merge branch 'ce-to-ee-2018-03-01' into 'master'

CE upstream - 2018-03-01 16:38 UTC

Closes gitaly#1032 and gitlab-ce#43278

See merge request gitlab-org/gitlab-ee!4788
parents 3cfd6fa5 d66b7003
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
......
......@@ -10,14 +10,22 @@ import DropdownUser from './dropdown_user';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) {
constructor({
baseEndpoint = '',
tokenizer,
page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys,
}) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = page === 'boards' && isGroup;
this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor;
this.setupMapping();
......@@ -60,7 +68,7 @@ export default class FilteredSearchDropdownManager {
reference: null,
gl: DropdownNonUser,
extraArguments: {
endpoint: `${this.baseEndpoint}/milestones.json${this.groupsOnly ? '?only_group_milestones=true' : ''}`,
endpoint: this.getMilestoneEndpoint(),
symbol: '%',
},
element: this.container.querySelector('#js-dropdown-milestone'),
......@@ -69,7 +77,7 @@ export default class FilteredSearchDropdownManager {
reference: null,
gl: DropdownNonUser,
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json${this.groupsOnly ? '?only_group_labels=true' : ''}`,
endpoint: this.getLabelsEndpoint(),
symbol: '~',
preprocessing: DropdownUtils.duplicateLabelPreprocessing,
},
......@@ -96,6 +104,28 @@ export default class FilteredSearchDropdownManager {
this.mapping = allowedMappings;
}
getMilestoneEndpoint() {
let endpoint = `${this.baseEndpoint}/milestones.json`;
// EE-only
if (this.groupsOnly) {
endpoint = `${endpoint}?only_group_milestones=true`;
}
return endpoint;
}
getLabelsEndpoint() {
let endpoint = `${this.baseEndpoint}/labels.json`;
// EE-only
if (this.groupsOnly) {
endpoint = `${endpoint}?only_group_labels=true`;
}
return endpoint;
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
......
......@@ -20,10 +20,13 @@ import DropdownUtils from './dropdown_utils';
export default class FilteredSearchManager {
constructor({
page,
isGroup = false,
isGroupAncestor = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = false;
this.isGroup = isGroup;
this.isGroupAncestor = isGroupAncestor;
this.states = ['opened', 'closed', 'merged', 'all'];
this.page = page;
......@@ -98,13 +101,14 @@ export default class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = FilteredSearchTokenizer;
this.dropdownManager = new FilteredSearchDropdownManager(
this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
this.tokenizer,
this.page,
this.isGroup,
this.filteredSearchTokenKeys,
);
this.dropdownManager = new FilteredSearchDropdownManager({
baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
tokenizer: this.tokenizer,
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
......
import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
export default ({ page, filteredSearchTokenKeys, stateFiltersSelector }) => {
export default ({
page,
filteredSearchTokenKeys,
isGroup,
isGroupAncestor,
stateFiltersSelector,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new FilteredSearchManager({
page,
isGroup,
isGroupAncestor,
filteredSearchTokenKeys,
stateFiltersSelector,
});
......
......@@ -316,7 +316,7 @@
v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Cancel"
title="Stop"
icon="close"
:pipeline-id="pipeline.id"
data-toggle="modal"
......
......@@ -14,6 +14,11 @@
collapsedCalendarIcon,
},
props: {
blockClass: {
type: String,
required: false,
default: '',
},
collapsed: {
type: Boolean,
required: false,
......@@ -91,7 +96,10 @@
</script>
<template>
<div class="block">
<div
class="block"
:class="blockClass"
>
<div class="issuable-sidebar-header">
<toggle-sidebar
:collapsed="collapsed"
......
class Profiles::PasswordsController < Profiles::ApplicationController
skip_before_action :check_password_expiration, only: [:new, :create]
skip_before_action :check_two_factor_requirement, only: [:new, :create]
before_action :set_user
before_action :authorize_change_password!
......
......@@ -146,7 +146,7 @@ class Repository
end
end
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil)
options = {
repo: raw_repository,
ref: ref,
......@@ -156,7 +156,8 @@ class Repository
after: after,
before: before,
follow: Array(path).length == 1,
skip_merges: skip_merges
skip_merges: skip_merges,
all: all
}
commits = Gitlab::Git::Commit.where(options)
......
---
title: Allow commits endpoint to work over all commits of a repository
merge_request: 17182
author:
type: added
---
title: Update tooltip on pipeline cancel to Stop (#42946)
merge_request: 17444
author:
type: fixed
---
title: Add NOT NULL constraint to projects.namespace_id
merge_request: 17448
author:
type: other
---
title: Removing the two factor check when the user sets a new password
merge_request: 17457
author:
type: fixed
---
title: Encode branch name as binary before creating a RPC request to copy attributes
merge_request: 17291
author:
type: fixed
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ChangeProjectNamespaceIdNotNull < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
class Project < ActiveRecord::Base
self.table_name = 'projects'
include EachBatch
end
BATCH_SIZE = 1000
DOWNTIME = false
disable_ddl_transaction!
def up
Project.where(namespace_id: nil).each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
change_column_null :projects, :namespace_id, false
end
def down
change_column_null :projects, :namespace_id, true
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180222043024) do
ActiveRecord::Schema.define(version: 20180301084653) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -1866,7 +1866,7 @@ ActiveRecord::Schema.define(version: 20180222043024) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
t.integer "namespace_id"
t.integer "namespace_id", null: false
t.datetime "last_activity_at"
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
......
......@@ -14,6 +14,9 @@ GET /projects/:id/repository/commits
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
......
......@@ -1204,6 +1204,7 @@ GET /projects/:id/hooks/:hook_id
"project_id": 3,
"push_events": true,
"issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
......@@ -1229,6 +1230,7 @@ POST /projects/:id/hooks
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events |
| `issues_events` | boolean | no | Trigger hook on issues events |
| `confidential_issues_events` | boolean | no | Trigger hook on confidential issues events |
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
......@@ -1253,6 +1255,7 @@ PUT /projects/:id/hooks/:hook_id
| `url` | string | yes | The hook URL |
| `push_events` | boolean | no | Trigger hook on push events |
| `issues_events` | boolean | no | Trigger hook on issues events |
| `confidential_issues_events` | boolean | no | Trigger hook on confidential issues events |
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
......
......@@ -619,6 +619,7 @@ Example response:
"active": true,
"push_events": true,
"issues_events": true,
"confidential_issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
......
......@@ -33,6 +33,40 @@ rest of the code should be as close to the CE files as possible.
[single code base]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2952#note_41016454
### Detection of EE-only files
For each commit (except on `master`), the `ee-files-location-check` CI job tries
to detect if there are any new files that are EE-only. If any file is detected,
the job fails with an explanation of why and what to do to make it pass.
Basically, the fix is simple: `git mv <file> ee/<file>`.
#### How to name your branches?
For any EE branch, the job will try to detect its CE counterpart by removing any
`ee-` prefix or `-ee` suffix from the EE branch name, and matching the last
branch that contains it.
For instance, from the EE branch `new-shiny-feature-ee` (or
`ee-new-shiny-feature`), the job would find the corresponding CE branches:
- `new-shiny-feature`
- `ce-new-shiny-feature`
- `new-shiny-feature-ce`
- `my-super-new-shiny-feature-in-ce`
#### Whitelist some EE-only files that cannot be moved to `ee/`
The `ee-files-location-check` CI job provides a whitelist of files or folders
that cannot or should not be moved to `ee/`. Feel free to open an issue to
discuss adding a new file/folder to this whitelist.
For instance, it was decided that moving EE-only files from `qa/` to `ee/qa/`
would make it difficult to build the `gitLab-{ce,ee}-qa` Docker images and it
was [not worth the complexity].
[not worth the complexity]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4997#note_59764702
### EE-only features
If the feature being developed is not present in any form in CE, we don't
......@@ -52,6 +86,11 @@ is applied not only to models. Here's a list of other examples:
- `ee/app/validators/foo_attr_validator.rb`
- `ee/app/workers/foo_worker.rb`
This works because for every path that are present in CE's eager-load/auto-load
paths, we add the same `ee/`-prepended path in [`config/application.rb`].
[`config/application.rb`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/d278b76d6600a0e27d8019a0be27971ba23ab640/config/application.rb#L41-51
### EE features based on CE features
For features that build on existing CE features, write a module in the
......
......@@ -36,6 +36,15 @@ If you are asynchronously adding content which contains lazy images then you nee
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
But in general it should be handled automatically through a `MutationObserver` in the lazy loading function.
### Animations
Only animate `opacity` & `transform` properties. Other properties (such as `top`, `left`, `margin`, and `padding`) all cause
Layout to be recalculated, which is much more expensive. For details on this, see "Styles that Affect Layout" in
[High Performance Animations][high-perf-animations].
If you _do_ need to change layout (e.g. a sidebar that pushes main content over), prefer [FLIP][flip] to change expensive
properties once, and handle the actual animation with transforms.
## Reducing Asset Footprint
### Page-specific JavaScript
......@@ -87,6 +96,7 @@ General tips:
- Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us).
- If some functionality can reasonably be achieved without adding extra libraries, avoid them.
- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages.
- [High Performance Animations][high-perf-animations]
-------
......@@ -105,3 +115,5 @@ General tips:
[d3]: https://d3js.org/
[chartjs]: http://www.chartjs.org/
[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
[high-perf-animations]: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
[flip]: https://aerotwist.com/blog/flip-your-animations/
......@@ -107,104 +107,28 @@ You can mark that content for translation with:
### JavaScript files
In JavaScript we added the `__()` (double underscore parenthesis) function
for translations.
In JavaScript we added the `__()` (double underscore parenthesis) function that
you can import from the `~/locale` file. For instance:
In order to test JavaScript translations you have to change the GitLab localization to other language than English and you have to generate JSON files using `bundle exec rake gettext:po_to_json` or `bundle exec rake gettext:compile`.
## Updating the PO files with the new content
Now that the new content is marked for translation, we need to update the PO
files with the following command:
```sh
bundle exec rake gettext:find
```
This command will update the `locale/gitlab.pot` file with the newly externalized
strings and remove any strings that aren't used anymore. You should check this
file in. Once the changes are on master, they will be picked up by
[Crowdin](http://translate.gitlab.com) and be presented for translation.
If there are merge conflicts in the `gitlab.pot` file, you can delete the file
and regenerate it using the same command. Confirm that you are not deleting any strings accidentally by looking over the diff.
The command also updates the translation files for each language: `locale/*/gitlab.po`
These changes can be discarded, the languange files will be updated by Crowdin
automatically.
Discard all of them at once like this:
```sh
git checkout locale/*/gitlab.po
```
### Validating PO files
To make sure we keep our translation files up to date, there's a linter that is
running on CI as part of the `static-analysis` job.
To lint the adjustments in PO files locally you can run `rake gettext:lint`.
The linter will take the following into account:
- Valid PO-file syntax
- Variable usage
- Only one unnamed (`%d`) variable, since the order of variables might change
in different languages
- All variables used in the message-id are used in the translation
- There should be no variables used in a translation that aren't in the
message-id
- Errors during translation.
The errors are grouped per file, and per message ID:
```
Errors in `locale/zh_HK/gitlab.po`:
PO-syntax errors
SimplePoParser::ParserErrorSyntax error in lines
Syntax error in msgctxt
Syntax error in msgid
Syntax error in msgstr
Syntax error in message_line
There should be only whitespace until the end of line after the double quote character of a message text.
Parseing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
SimplePoParser filtered backtrace: SimplePoParser::ParserError
Errors in `locale/zh_TW/gitlab.po`:
1 pipeline
<%d 條流水線> is using unknown variables: [%d]
Failure translating to zh_TW with []: too few arguments
```js
import { __ } from '~/locale';
const label = __('Subscribe');
```
In this output the `locale/zh_HK/gitlab.po` has syntax errors.
The `locale/zh_TW/gitlab.po` has variables that are used in the translation that
aren't in the message with id `1 pipeline`.
## Working with special content
### Just marking content for parsing
- In Ruby/HAML:
```ruby
_('Subscribe')
```
- In JavaScript:
```js
import { __ } from '../../../locale';
const label = __('Subscribe');
```
In order to test JavaScript translations you have to change the GitLab
localization to other language than English and you have to generate JSON files
using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
### Dynamic translations
Sometimes there are some dynamic translations that can't be found by the
parser when running `bundle exec rake gettext:find`. For these scenarios you can
use the [`_N` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
parser when running `bin/rake gettext:find`. For these scenarios you can
use the [`N_` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a).
## Working with special content
### Interpolation
- In Ruby/HAML:
......@@ -216,7 +140,7 @@ There is also and alternative method to [translate messages from validation erro
- In JavaScript:
```js
import { __, sprintf } from '../../../locale';
import { __, sprintf } from '~/locale';
sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe'
```
......@@ -228,24 +152,30 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
- In Ruby/HAML:
```ruby
n_('Apple', 'Apples', 3) => 'Apples'
n_('Apple', 'Apples', 3)
# => 'Apples'
```
Using interpolation:
```ruby
n_("There is a mouse.", "There are %d mice.", size) % size
# => When size == 1: 'There is a mouse.'
# => When size == 2: 'There are 2 mice.'
```
- In JavaScript:
```js
n__('Apple', 'Apples', 3) => 'Apples'
n__('Apple', 'Apples', 3)
// => 'Apples'
```
Using interpolation:
```js
n__('Last day', 'Last %d days', 30) => 'Last 30 days'
n__('Last day', 'Last %d days', x)
// => When x == 1: 'Last day'
// => When x == 2: 'Last 2 days'
```
### Namespaces
......@@ -267,12 +197,15 @@ Sometimes you need to add some context to the text that you want to translate
s__('OpenedNDaysAgo|Opened')
```
Note: The namespace should be removed from the translation. See the [translation
guidelines for more details](./translation.md#namespaced-strings).
### Dates / times
- In JavaScript:
```js
import { createDateTimeFormat } from '.../locale';
import { createDateTimeFormat } from '~/locale';
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
console.log(dateFormat.format(new Date('2063-04-05'))) // April 5, 2063
......@@ -282,6 +215,100 @@ This makes use of [`Intl.DateTimeFormat`].
[`Intl.DateTimeFormat`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
## Best practices
### Splitting sentences
Please never split a sentence as that would assume the sentence grammar and
structure is the same in all languages.
For instance, the following
```js
{{ s__("mrWidget|Set by") }}
{{ author.name }}
{{ s__("mrWidget|to be merged automatically when the pipeline succeeds") }}
```
should be externalized as follows:
```js
{{ sprintf(s__("mrWidget|Set by %{author} to be merged automatically when the pipeline succeeds"), { author: author.name }) }}
```
When in doubt, try to follow the best practices described in this [Mozilla
Developer documentation][mdn].
[mdn]: https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_content_best_practices#Splitting
## Updating the PO files with the new content
Now that the new content is marked for translation, we need to update the PO
files with the following command:
```sh
bin/rake gettext:find
```
This command will update the `locale/gitlab.pot` file with the newly externalized
strings and remove any strings that aren't used anymore. You should check this
file in. Once the changes are on master, they will be picked up by
[Crowdin](http://translate.gitlab.com) and be presented for translation.
If there are merge conflicts in the `gitlab.pot` file, you can delete the file
and regenerate it using the same command. Confirm that you are not deleting any strings accidentally by looking over the diff.
The command also updates the translation files for each language: `locale/*/gitlab.po`
These changes can be discarded, the languange files will be updated by Crowdin
automatically.
Discard all of them at once like this:
```sh
git checkout locale/*/gitlab.po
```
### Validating PO files
To make sure we keep our translation files up to date, there's a linter that is
running on CI as part of the `static-analysis` job.
To lint the adjustments in PO files locally you can run `rake gettext:lint`.
The linter will take the following into account:
- Valid PO-file syntax
- Variable usage
- Only one unnamed (`%d`) variable, since the order of variables might change
in different languages
- All variables used in the message-id are used in the translation
- There should be no variables used in a translation that aren't in the
message-id
- Errors during translation.
The errors are grouped per file, and per message ID:
```
Errors in `locale/zh_HK/gitlab.po`:
PO-syntax errors
SimplePoParser::ParserErrorSyntax error in lines
Syntax error in msgctxt
Syntax error in msgid
Syntax error in msgstr
Syntax error in message_line
There should be only whitespace until the end of line after the double quote character of a message text.
Parseing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
SimplePoParser filtered backtrace: SimplePoParser::ParserError
Errors in `locale/zh_TW/gitlab.po`:
1 pipeline
<%d 條流水線> is using unknown variables: [%d]
Failure translating to zh_TW with []: too few arguments
```
In this output the `locale/zh_HK/gitlab.po` has syntax errors.
The `locale/zh_TW/gitlab.po` has variables that are used in the translation that
aren't in the message with id `1 pipeline`.
## Adding a new language
Let's suppose you want to add translations for a new language, let's say French.
......@@ -300,14 +327,14 @@ Let's suppose you want to add translations for a new language, let's say French.
1. Next, you need to add the language:
```sh
bundle exec rake gettext:add_language[fr]
bin/rake gettext:add_language[fr]
```
If you want to add a new language for a specific region, the command is similar,
you just need to separate the region with an underscore (`_`). For example:
```sh
bundle exec rake gettext:add_language[en_GB]
bin/rake gettext:add_language[en_GB]
```
Please note that you need to specify the region part in capitals.
......@@ -321,7 +348,7 @@ Let's suppose you want to add translations for a new language, let's say French.
containing the translations:
```sh
bundle exec rake gettext:compile
bin/rake gettext:compile
```
1. In order to see the translated content we need to change our preferred language
......
......@@ -15,6 +15,7 @@ are very appreciative of the work done by translators and proofreaders!
- Dutch
- Esperanto
- French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German
- Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
......
......@@ -37,33 +37,43 @@ Comments can be added to discuss a translation with the community.
Remember to **Save** each translation.
## Translation Guidelines
## General Translation Guidelines
Be sure to check the following guidelines before you translate any strings.
### Namespaced strings
When an externalized string is prepended with a namespace, e.g.
`s_('OpenedNDaysAgo|Opened')`, the namespace should be removed from the final
translation.
For example in French `OpenedNDaysAgo|Opened` would be translated to
`Ouvert•e`, not `OpenedNDaysAgo|Ouvert•e`.
### Technical terms
Technical terms should be treated like proper nouns and not be translated.
This helps maintain a logical connection and consistency between tools (e.g. `git` client) and
GitLab.
Some technical terms should be treated like proper nouns and not be translated.
Technical terms that should always be in English are noted in the glossary when using
[translate.gitlab.com](https://translate.gitlab.com).
Technical terms that should always be in English are noted in the glossary when
using [translate.gitlab.com](https://translate.gitlab.com).
This helps maintain a logical connection and consistency between tools (e.g.
`git` client) and GitLab.
### Formality
The level of formality used in software varies by language.
For example, in French we translate `you` as the informal `tu`.
For example, in French we translate `you` as the formal `vous`.
You can refer to other translated strings and notes in the glossary to assist determining a
suitable level of formality.
You can refer to other translated strings and notes in the glossary to assist
determining a suitable level of formality.
### Inclusive language
[Diversity] is one of GitLab's values.
We ask you to avoid translations which exclude people based on their gender or ethnicity.
In languages which distinguish between a male and female form,
use both or choose a neutral formulation.
We ask you to avoid translations which exclude people based on their gender or
ethnicity.
In languages which distinguish between a male and female form, use both or
choose a neutral formulation.
For example in German, the word "user" can be translated into "Benutzer" (male) or "Benutzerin" (female).
Therefore "create a new user" would translate into "Benutzer(in) anlegen".
......@@ -74,3 +84,14 @@ Therefore "create a new user" would translate into "Benutzer(in) anlegen".
To propose additions to the glossary please
[open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues).
## French Translation Guidelines
### Inclusive language in French
In French, we should follow the guidelines from [ecriture-inclusive.fr]. For
instance:
- Utilisateur•rice•s
[ecriture-inclusive.fr]: http://www.ecriture-inclusive.fr/
......@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz
echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz
cd git-2.14.3/
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz
echo '9acc4339b7a2ab484eea69d705923271682b7058015219cf5a7e6ed8dee5b5fb git-2.16.2.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.2.tar.gz
cd git-2.16.2/
./configure
make prefix=/usr/local all
......
......@@ -18,25 +18,28 @@ module API
optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
optional :path, type: String, desc: 'The file path'
optional :all, type: Boolean, desc: 'Every commit will be returned'
use :pagination
end
get ':id/repository/commits' do
path = params[:path]
before = params[:until]
after = params[:since]
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
commits = user_project.repository.commits(ref,
path: path,
limit: params[:per_page],
offset: offset,
before: before,
after: after)
after: after,
all: all)
commit_count =
if path || before || after
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after)
if all || path || before || after
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all)
else
# Cacheable commit count.
user_project.repository.commit_count_for_ref(ref)
......
......@@ -71,7 +71,7 @@ module API
end
class ProjectHook < Hook
expose :project_id, :issues_events
expose :project_id, :issues_events, :confidential_issues_events
expose :note_events, :pipeline_events, :wiki_page_events
expose :job_events
end
......
......@@ -10,6 +10,7 @@ module API
requires :url, type: String, desc: "The URL to send the request to"
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
optional :confidential_issues_events, type: Boolean, desc: "Trigger hook on confidential issues events"
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
......
......@@ -260,8 +260,9 @@ module API
class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :merge_requests_events
expose :tag_push_events, :note_events, :pipeline_events
expose :push_events, :issues_events, :confidential_issues_events
expose :merge_requests_events, :tag_push_events, :note_events
expose :pipeline_events
expose :job_events, as: :build_events
# Expose serialized properties
expose :properties do |service, options|
......@@ -270,8 +271,9 @@ module API
end
class ProjectHook < ::API::Entities::Hook
expose :project_id, :issues_events, :merge_requests_events
expose :note_events, :pipeline_events, :wiki_page_events
expose :project_id, :issues_events, :confidential_issues_events
expose :merge_requests_events, :note_events, :pipeline_events
expose :wiki_page_events
expose :job_events, as: :build_events
end
......
......@@ -11,6 +11,7 @@ module API
requires :url, type: String, desc: "The URL to send the request to"
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
optional :confidential_issues_events, type: Boolean, desc: "Trigger hook on confidential issues events"
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
......
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Base
def evaluate(**variables)
raise NotImplementedError
end
def self.build(token)
raise NotImplementedError
end
def self.scan(scanner)
if scanner.scan(self::PATTERN)
Expression::Token.new(scanner.matched, self)
end
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Equals < Lexeme::Operator
PATTERN = /==/.freeze
def initialize(left, right)
@left = left
@right = right
end
def evaluate(variables = {})
@left.evaluate(variables) == @right.evaluate(variables)
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Null < Lexeme::Value
PATTERN = /null/.freeze
def initialize(value = nil)
@value = nil
end
def evaluate(variables = {})
nil
end
def self.build(_value)
self.new
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Operator < Lexeme::Base
def self.type
:operator
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class String < Lexeme::Value
PATTERN = /("(?<string>.+?)")|('(?<string>.+?)')/.freeze
def initialize(value)
@value = value
end
def evaluate(variables = {})
@value.to_s
end
def self.build(string)
new(string.match(PATTERN)[:string])
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Value < Lexeme::Base
def self.type
:value
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class Variable < Lexeme::Value
PATTERN = /\$(?<name>\w+)/.freeze
def initialize(name)
@name = name
end
def evaluate(variables = {})
HashWithIndifferentAccess.new(variables).fetch(@name, nil)
end
def self.build(string)
new(string.match(PATTERN)[:name])
end
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
class Lexer
include ::Gitlab::Utils::StrongMemoize
LEXEMES = [
Expression::Lexeme::Variable,
Expression::Lexeme::String,
Expression::Lexeme::Null,
Expression::Lexeme::Equals
].freeze
SyntaxError = Class.new(Statement::StatementError)
MAX_TOKENS = 100
def initialize(statement, max_tokens: MAX_TOKENS)
@scanner = StringScanner.new(statement)
@max_tokens = max_tokens
end
def tokens
strong_memoize(:tokens) { tokenize }
end
def lexemes
tokens.map(&:to_lexeme)
end
private
def tokenize
tokens = []
@max_tokens.times do
@scanner.skip(/\s+/) # ignore whitespace
return tokens if @scanner.eos?
lexeme = LEXEMES.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
end
unless lexeme.present?
raise Lexer::SyntaxError, 'Unknown lexeme found!'
end
end
raise Lexer::SyntaxError, 'Too many tokens!'
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
class Parser
def initialize(tokens)
@tokens = tokens.to_enum
@nodes = []
end
##
# This produces a reverse descent parse tree.
#
# It currently does not support precedence of operators.
#
def tree
while token = @tokens.next
case token.type
when :operator
token.build(@nodes.pop, tree).tap do |node|
@nodes.push(node)
end
when :value
token.build.tap do |leaf|
@nodes.push(leaf)
end
end
end
rescue StopIteration
@nodes.last || Lexeme::Null.new
end
def self.seed(statement)
new(Expression::Lexer.new(statement).tokens)
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
class Statement
StatementError = Class.new(StandardError)
GRAMMAR = [
%w[variable equals string],
%w[variable equals variable],
%w[variable equals null],
%w[string equals variable],
%w[null equals variable],
%w[variable]
].freeze
def initialize(statement, pipeline)
@lexer = Expression::Lexer.new(statement)
@variables = pipeline.variables.map do |variable|
[variable.key, variable.value]
end
end
def parse_tree
raise StatementError if @lexer.lexemes.empty?
unless GRAMMAR.find { |syntax| syntax == @lexer.lexemes }
raise StatementError, 'Unknown pipeline expression!'
end
Expression::Parser.new(@lexer.tokens).tree
end
def evaluate
parse_tree.evaluate(@variables.to_h)
end
end
end
end
end
end
module Gitlab
module Ci
module Pipeline
module Expression
class Token
attr_reader :value, :lexeme
def initialize(value, lexeme)
@value = value
@lexeme = lexeme
end
def build(*args)
@lexeme.build(@value, *args)
end
def type
@lexeme.type
end
def to_lexeme
@lexeme.name.demodulize.downcase
end
end
end
end
end
end
......@@ -467,7 +467,8 @@ module Gitlab
follow: false,
skip_merges: false,
after: nil,
before: nil
before: nil,
all: false
}
options = default_options.merge(options)
......@@ -478,8 +479,9 @@ module Gitlab
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end
# TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled
if is_enabled && !options[:all]
gitaly_commit_client.find_commits(options)
else
raw_log(options).map { |c| Commit.decorate(self, c) }
......@@ -489,13 +491,16 @@ module Gitlab
# Used in gitaly-ruby
def raw_log(options)
actual_ref = options[:ref] || root_ref
begin
sha = sha_from_ref(actual_ref)
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
# Return an empty array if the ref wasn't found
return []
end
sha =
unless options[:all]
actual_ref = options[:ref] || root_ref
begin
sha_from_ref(actual_ref)
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
# Return an empty array if the ref wasn't found
return []
end
end
log_by_shell(sha, options)
end
......@@ -503,8 +508,9 @@ module Gitlab
def count_commits(options)
count_commits_options = process_count_commits_options(options)
# TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
gitaly_migrate(:count_commits) do |is_enabled|
if is_enabled
if is_enabled && !options[:all]
count_commits_by_gitaly(count_commits_options)
else
count_commits_by_shelling_out(count_commits_options)
......@@ -1705,7 +1711,12 @@ module Gitlab
cmd << '--no-merges' if options[:skip_merges]
cmd << "--after=#{options[:after].iso8601}" if options[:after]
cmd << "--before=#{options[:before].iso8601}" if options[:before]
cmd << sha
if options[:all]
cmd += %w[--all --reverse]
else
cmd << sha
end
# :path can be a string or an array of strings
if options[:path].present?
......@@ -1922,7 +1933,16 @@ module Gitlab
cmd << "--before=#{options[:before].iso8601}" if options[:before]
cmd << "--max-count=#{options[:max_count]}" if options[:max_count]
cmd << "--left-right" if options[:left_right]
cmd += %W[--count #{options[:ref]}]
cmd << '--count'
cmd << if options[:all]
'--all'
elsif options[:ref]
options[:ref]
else
raise ArgumentError, "Please specify a valid ref or set the 'all' attribute to true"
end
cmd += %W[-- #{options[:path]}] if options[:path].present?
cmd
end
......
......@@ -41,7 +41,7 @@ module Gitlab
end
def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end
......
......@@ -134,5 +134,15 @@ describe 'Profile > Password' do
expect(current_path).to eq new_user_session_path
end
context 'when global require_two_factor_authentication is enabled' do
it 'needs change user password' do
stub_application_setting(require_two_factor_authentication: true)
visit profile_path
expect(current_path).to eq new_profile_password_path
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:left) { double('left') }
let(:right) { double('right') }
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('==', left, right))
.to be_a(described_class)
end
end
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
end
end
describe '#evaluate' do
it 'returns false when left and right are not equal' do
allow(left).to receive(:evaluate).and_return(1)
allow(right).to receive(:evaluate).and_return(2)
operator = described_class.new(left, right)
expect(operator.evaluate(VARIABLE: 3)).to eq false
end
it 'returns true when left and right are equal' do
allow(left).to receive(:evaluate).and_return(1)
allow(right).to receive(:evaluate).and_return(1)
operator = described_class.new(left, right)
expect(operator.evaluate(VARIABLE: 3)).to eq true
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('null'))
.to be_a(described_class)
end
end
describe '.type' do
it 'is a value lexeme' do
expect(described_class.type).to eq :value
end
end
describe '#evaluate' do
it 'always evaluates to `nil`' do
expect(described_class.new('null').evaluate).to be_nil
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('"my string"'))
.to be_a(described_class)
end
end
describe '.type' do
it 'is a value lexeme' do
expect(described_class.type).to eq :value
end
end
describe '.scan' do
context 'when using double quotes' do
it 'correctly identifies string token' do
scanner = StringScanner.new('"some string"')
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some string'
end
end
context 'when using single quotes' do
it 'correctly identifies string token' do
scanner = StringScanner.new("'some string 2'")
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some string 2'
end
end
context 'when there are mixed quotes in the string' do
it 'is a greedy scanner for double quotes' do
scanner = StringScanner.new('"some string" "and another one"')
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some string'
end
it 'is a greedy scanner for single quotes' do
scanner = StringScanner.new("'some string' 'and another one'")
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some string'
end
it 'allows to use single quotes inside double quotes' do
scanner = StringScanner.new(%("some ' string"))
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq "some ' string"
end
it 'allow to use double quotes inside single quotes' do
scanner = StringScanner.new(%('some " string'))
token = described_class.scan(scanner)
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some " string'
end
end
end
describe '#evaluate' do
it 'returns string value it is is present' do
string = described_class.new('my string')
expect(string.evaluate).to eq 'my string'
end
it 'returns an empty string if it is empty' do
string = described_class.new('')
expect(string.evaluate).to eq ''
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('$VARIABLE'))
.to be_a(described_class)
end
end
describe '.type' do
it 'is a value lexeme' do
expect(described_class.type).to eq :value
end
end
describe '#evaluate' do
it 'returns variable value if it is defined' do
variable = described_class.new('VARIABLE')
expect(variable.evaluate(VARIABLE: 'my variable'))
.to eq 'my variable'
end
it 'allows to use a string as a variable key too' do
variable = described_class.new('VARIABLE')
expect(variable.evaluate('VARIABLE' => 'my variable'))
.to eq 'my variable'
end
it 'returns nil if it is not defined' do
variable = described_class.new('VARIABLE')
expect(variable.evaluate(OTHER: 'variable')).to be_nil
end
it 'returns an empty string if it is empty' do
variable = described_class.new('VARIABLE')
expect(variable.evaluate(VARIABLE: '')).to eq ''
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexer do
let(:token_class) do
Gitlab::Ci::Pipeline::Expression::Token
end
describe '#tokens' do
it 'tokenss single value' do
tokens = described_class.new('$VARIABLE').tokens
expect(tokens).to be_one
expect(tokens).to all(be_an_instance_of(token_class))
end
it 'does ignore whitespace characters' do
tokens = described_class.new("\t$VARIABLE ").tokens
expect(tokens).to be_one
expect(tokens).to all(be_an_instance_of(token_class))
end
it 'tokenss multiple values of the same token' do
tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokens
expect(tokens.size).to eq 2
expect(tokens).to all(be_an_instance_of(token_class))
end
it 'tokenss multiple values with different tokens' do
tokens = described_class.new('$VARIABLE "text" "value"').tokens
expect(tokens.size).to eq 3
expect(tokens.first.value).to eq '$VARIABLE'
expect(tokens.second.value).to eq '"text"'
expect(tokens.third.value).to eq '"value"'
end
it 'tokenss tokens and operators' do
tokens = described_class.new('$VARIABLE == "text"').tokens
expect(tokens.size).to eq 3
expect(tokens.first.value).to eq '$VARIABLE'
expect(tokens.second.value).to eq '=='
expect(tokens.third.value).to eq '"text"'
end
it 'limits statement to specified amount of tokens' do
lexer = described_class.new("$V1 $V2 $V3 $V4", max_tokens: 3)
expect { lexer.tokens }
.to raise_error described_class::SyntaxError
end
it 'raises syntax error in case of finding unknown tokens' do
lexer = described_class.new('$V1 123 $V2')
expect { lexer.tokens }
.to raise_error described_class::SyntaxError
end
end
describe '#lexemes' do
it 'returns an array of syntax lexemes' do
lexer = described_class.new('$VAR "text"')
expect(lexer.lexemes).to eq %w[variable string]
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
context 'when using operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
end
context 'when using a single token' do
it 'returns a single token instance' do
expect(described_class.seed('$VAR').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
context 'when expression is empty' do
it 'returns a null token' do
expect(described_class.seed('').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Statement do
let(:pipeline) { build(:ci_pipeline) }
subject do
described_class.new(text, pipeline)
end
before do
pipeline.variables.build([key: 'VARIABLE', value: 'my variable'])
end
describe '#parse_tree' do
context 'when expression is empty' do
let(:text) { '' }
it 'raises an error' do
expect { subject.parse_tree }
.to raise_error described_class::StatementError
end
end
context 'when expression grammar is incorrect' do
table = [
'$VAR "text"', # missing operator
'== "123"', # invalid right side
"'single quotes'", # single quotes string
'$VAR ==', # invalid right side
'12345', # unknown syntax
'' # empty statement
]
table.each do |syntax|
it "raises an error when syntax is `#{syntax}`" do
expect { described_class.new(syntax, pipeline).parse_tree }
.to raise_error described_class::StatementError
end
end
end
context 'when expression grammar is correct' do
context 'when using an operator' do
let(:text) { '$VAR == "value"' }
it 'returns a reverse descent parse tree' do
expect(subject.parse_tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
end
context 'when using a single token' do
let(:text) { '$VARIABLE' }
it 'returns a single token instance' do
expect(subject.parse_tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
end
end
describe '#evaluate' do
statements = [
['$VARIABLE == "my variable"', true],
["$VARIABLE == 'my variable'", true],
['"my variable" == $VARIABLE', true],
['$VARIABLE == null', false],
['$VAR == null', true],
['null == $VAR', true],
['$VARIABLE', 'my variable'],
['$VAR', nil]
]
statements.each do |expression, value|
context "when using expression `#{expression}`" do
let(:text) { expression }
it "evaluates to `#{value.inspect}`" do
expect(subject.evaluate).to eq value
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Token do
let(:value) { '$VARIABLE' }
let(:lexeme) { Gitlab::Ci::Pipeline::Expression::Lexeme::Variable }
subject { described_class.new(value, lexeme) }
describe '#value' do
it 'returns raw token value' do
expect(subject.value).to eq value
end
end
describe '#lexeme' do
it 'returns raw token lexeme' do
expect(subject.lexeme).to eq lexeme
end
end
describe '#build' do
it 'delegates to lexeme after adding a value' do
expect(lexeme).to receive(:build)
.with(value, 'some', 'args')
subject.build('some', 'args')
end
it 'allows passing only required arguments' do
expect(subject.build).to be_an_instance_of(lexeme)
end
end
describe '#type' do
it 'delegates type query to the lexeme' do
expect(subject.type).to eq :value
end
end
describe '#to_lexeme' do
it 'returns raw lexeme syntax component name' do
expect(subject.to_lexeme).to eq 'variable'
end
end
end
......@@ -895,7 +895,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "encoding"))
end
it "should not follow renames" do
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
......@@ -907,7 +907,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "encoding/CHANGELOG"))
end
it "should not follow renames" do
it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
......@@ -919,7 +919,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "CHANGELOG"))
end
it "should not follow renames" do
it "does not follow renames" do
expect(log_commits).to include(commit_with_old_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_new_name)
......@@ -931,7 +931,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
end
it "should return a list of commits" do
it "returns a list of commits" do
expect(log_commits.size).to eq(1)
end
end
......@@ -991,6 +991,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end
end
context 'with all' do
let(:options) { { all: true, limit: 50 } }
it 'returns a list of commits' do
commits = repository.log(options)
expect(commits.size).to eq(37)
end
end
end
describe "#rugged_commits_between" do
......@@ -1134,6 +1144,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do
it_behaves_like 'extended commit counting'
context "with all" do
it "returns the number of commits in the whole repository" do
options = { all: true }
expect(repository.count_commits(options)).to eq(34)
end
end
context 'without all or ref being specified' do
it "raises an ArgumentError" do
expect { repository.count_commits({}) }.to raise_error(ArgumentError, "Please specify a valid ref or set the 'all' attribute to true")
end
end
end
end
......@@ -1406,79 +1430,95 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#copy_gitattributes" do
let(:attributes_path) { File.join(SEED_STORAGE_PATH, TEST_REPO_PATH, 'info/attributes') }
shared_examples 'applying git attributes' do
let(:attributes_path) { File.join(SEED_STORAGE_PATH, TEST_REPO_PATH, 'info/attributes') }
it "raises an error with invalid ref" do
expect { repository.copy_gitattributes("invalid") }.to raise_error(Gitlab::Git::Repository::InvalidRef)
end
context "with no .gitattrbutes" do
before do
repository.copy_gitattributes("master")
after do
FileUtils.rm_rf(attributes_path) if Dir.exist?(attributes_path)
end
it "does not have an info/attributes" do
expect(File.exist?(attributes_path)).to be_falsey
it "raises an error with invalid ref" do
expect { repository.copy_gitattributes("invalid") }.to raise_error(Gitlab::Git::Repository::InvalidRef)
end
after do
FileUtils.rm_rf(attributes_path)
end
end
context 'when forcing encoding issues' do
let(:branch_name) { "ʕ•ᴥ•ʔ" }
context "with .gitattrbutes" do
before do
repository.copy_gitattributes("gitattributes")
end
before do
repository.create_branch(branch_name, "master")
end
it "has an info/attributes" do
expect(File.exist?(attributes_path)).to be_truthy
end
after do
repository.rm_branch(branch_name, user: build(:admin))
end
it "has the same content in info/attributes as .gitattributes" do
contents = File.open(attributes_path, "rb") { |f| f.read }
expect(contents).to eq("*.md binary\n")
end
it "doesn't raise with a valid unicode ref" do
expect { repository.copy_gitattributes(branch_name) }.not_to raise_error
after do
FileUtils.rm_rf(attributes_path)
repository
end
end
end
context "with updated .gitattrbutes" do
before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("gitattributes-updated")
end
context "with no .gitattrbutes" do
before do
repository.copy_gitattributes("master")
end
it "has an info/attributes" do
expect(File.exist?(attributes_path)).to be_truthy
it "does not have an info/attributes" do
expect(File.exist?(attributes_path)).to be_falsey
end
end
it "has the updated content in info/attributes" do
contents = File.read(attributes_path)
expect(contents).to eq("*.txt binary\n")
end
context "with .gitattrbutes" do
before do
repository.copy_gitattributes("gitattributes")
end
after do
FileUtils.rm_rf(attributes_path)
end
end
it "has an info/attributes" do
expect(File.exist?(attributes_path)).to be_truthy
end
context "with no .gitattrbutes in HEAD but with previous info/attributes" do
before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("master")
it "has the same content in info/attributes as .gitattributes" do
contents = File.open(attributes_path, "rb") { |f| f.read }
expect(contents).to eq("*.md binary\n")
end
end
it "does not have an info/attributes" do
expect(File.exist?(attributes_path)).to be_falsey
context "with updated .gitattrbutes" do
before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("gitattributes-updated")
end
it "has an info/attributes" do
expect(File.exist?(attributes_path)).to be_truthy
end
it "has the updated content in info/attributes" do
contents = File.read(attributes_path)
expect(contents).to eq("*.txt binary\n")
end
end
after do
FileUtils.rm_rf(attributes_path)
context "with no .gitattrbutes in HEAD but with previous info/attributes" do
before do
repository.copy_gitattributes("gitattributes")
repository.copy_gitattributes("master")
end
it "does not have an info/attributes" do
expect(File.exist?(attributes_path)).to be_falsey
end
end
end
context 'when gitaly is enabled' do
it_behaves_like 'applying git attributes'
end
context 'when gitaly is disabled', :disable_gitaly do
it_behaves_like 'applying git attributes'
end
end
describe '#ref_exists?' do
......
......@@ -29,6 +29,7 @@ describe Gitlab::ImportExport::RelationFactory do
'service_id' => service_id,
'push_events' => true,
'issues_events' => false,
'confidential_issues_events' => false,
'merge_requests_events' => true,
'tag_push_events' => false,
'note_events' => true,
......
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb')
describe CleanupNamespacelessPendingDeleteProjects do
describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do
before do
# Stub after_save callbacks that will fail when Project has no namespace
allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
......
......@@ -8,7 +8,7 @@ describe MigrateIssuesToGhostUser, :migration do
let(:users) { table(:users) }
before do
project = projects.create!(name: 'gitlab')
project = projects.create!(name: 'gitlab', namespace_id: 1)
user = users.create(email: 'test@example.com')
issues.create(title: 'Issue 1', author_id: nil, project_id: project.id)
issues.create(title: 'Issue 2', author_id: user.id, project_id: project.id)
......
......@@ -2909,7 +2909,8 @@ describe Project do
end
it 'is a no-op when there is no namespace' do
project.update_column(:namespace_id, nil)
project.namespace.delete
project.reload
expect_any_instance_of(Projects::UpdatePagesConfigurationService).not_to receive(:execute)
expect_any_instance_of(Gitlab::PagesTransfer).not_to receive(:rename_project)
......@@ -2941,7 +2942,8 @@ describe Project do
it 'is a no-op on legacy projects when there is no namespace' do
export_path = legacy_project.export_path
legacy_project.update_column(:namespace_id, nil)
legacy_project.namespace.delete
legacy_project.reload
expect(FileUtils).not_to receive(:rm_rf).with(export_path)
......@@ -2953,7 +2955,8 @@ describe Project do
it 'runs on hashed storage projects when there is no namespace' do
export_path = project.export_path
project.update_column(:namespace_id, nil)
project.namespace.delete
legacy_project.reload
allow(FileUtils).to receive(:rm_rf).and_call_original
expect(FileUtils).to receive(:rm_rf).with(export_path).and_call_original
......
......@@ -242,23 +242,51 @@ describe Repository do
end
describe '#commits' do
it 'sets follow when path is a single path' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
repository.commits('master', limit: 1, path: 'README.md')
repository.commits('master', limit: 1, path: ['README.md'])
context 'when neither the all flag nor a ref are specified' do
it 'returns every commit from default branch' do
expect(repository.commits(limit: 60).size).to eq(37)
end
end
it 'does not set follow when path is multiple paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
context 'when ref is passed' do
it 'returns every commit from the specified ref' do
expect(repository.commits('master', limit: 60).size).to eq(37)
end
repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
end
context 'when all' do
it 'returns every commit from the repository' do
expect(repository.commits('master', limit: 60, all: true).size).to eq(60)
end
end
context 'with path' do
it 'sets follow when it is a single path' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
repository.commits('master', limit: 1, path: 'README.md')
repository.commits('master', limit: 1, path: ['README.md'])
end
it 'does not set follow when there are no paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
it 'does not set follow when it is multiple paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
repository.commits('master', limit: 1)
repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
end
end
context 'without path' do
it 'does not set follow' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
repository.commits('master', limit: 1)
end
end
end
context "when 'all' flag is set" do
it 'returns every commit from the repository' do
expect(repository.commits(all: true, limit: 60).size).to eq(60)
end
end
end
......
......@@ -149,6 +149,18 @@ describe API::Commits do
end
end
context 'all optional parameter' do
it 'returns all project commits' do
commit_count = project.repository.count_commits(all: true)
get api("/projects/#{project_id}/repository/commits?all=true", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count.to_s)
expect(response.headers['X-Page']).to eql('1')
end
end
context 'with pagination params' do
let(:page) { 1 }
let(:per_page) { 5 }
......
......@@ -28,6 +28,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response.count).to eq(1)
expect(json_response.first['url']).to eq("http://example.com")
expect(json_response.first['issues_events']).to eq(true)
expect(json_response.first['confidential_issues_events']).to eq(true)
expect(json_response.first['push_events']).to eq(true)
expect(json_response.first['merge_requests_events']).to eq(true)
expect(json_response.first['tag_push_events']).to eq(true)
......@@ -56,6 +57,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['url']).to eq(hook.url)
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(hook.push_events)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
......@@ -90,13 +92,14 @@ describe API::ProjectHooks, 'ProjectHooks' do
it "adds hook to project" do
expect do
post api("/projects/#{project.id}/hooks", user),
url: "http://example.com", issues_events: true, wiki_page_events: true,
url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true,
job_events: true
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['url']).to eq('http://example.com')
expect(json_response['issues_events']).to eq(true)
expect(json_response['confidential_issues_events']).to eq(true)
expect(json_response['push_events']).to eq(true)
expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false)
......@@ -144,6 +147,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['url']).to eq('http://example.org')
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(false)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
......
......@@ -27,6 +27,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response.count).to eq(1)
expect(json_response.first['url']).to eq("http://example.com")
expect(json_response.first['issues_events']).to eq(true)
expect(json_response.first['confidential_issues_events']).to eq(true)
expect(json_response.first['push_events']).to eq(true)
expect(json_response.first['merge_requests_events']).to eq(true)
expect(json_response.first['tag_push_events']).to eq(true)
......@@ -54,6 +55,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['url']).to eq(hook.url)
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(hook.push_events)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
......@@ -87,12 +89,13 @@ describe API::ProjectHooks, 'ProjectHooks' do
it "adds hook to project" do
expect do
post v3_api("/projects/#{project.id}/hooks", user),
url: "http://example.com", issues_events: true, wiki_page_events: true, build_events: true
url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true, build_events: true
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['url']).to eq('http://example.com')
expect(json_response['issues_events']).to eq(true)
expect(json_response['confidential_issues_events']).to eq(true)
expect(json_response['push_events']).to eq(true)
expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false)
......@@ -139,6 +142,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['url']).to eq('http://example.org')
expect(json_response['issues_events']).to eq(hook.issues_events)
expect(json_response['confidential_issues_events']).to eq(hook.confidential_issues_events)
expect(json_response['push_events']).to eq(false)
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
......
# pack-refs with: peeled fully-peeled
# pack-refs with: peeled fully-peeled sorted
0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature
12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix
6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path
......
......@@ -22,7 +22,9 @@ describe NamespacelessProjectDestroyWorker do
end
end
context 'project has no namespace' do
# Only possible with schema 20180222043024 and lower.
# Project#namespace_id has not null constraint since then
context 'project has no namespace', :migration, schema: 20180222043024 do
let!(:project) do
project = build(:project, namespace_id: nil)
project.save(validate: false)
......
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