summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md53
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue16
-rw-r--r--app/assets/javascripts/diffs/components/changed_files.vue41
-rw-r--r--app/assets/javascripts/diffs/components/changed_files_dropdown.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue37
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue21
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/actions.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue2
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/import/manifest_controller.rb93
-rw-r--r--app/helpers/namespaces_helper.rb11
-rw-r--r--app/models/notification_setting.rb1
-rw-r--r--app/models/project_wiki.rb5
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/services/groups/nested_create_service.rb12
-rw-r--r--app/services/metrics_service.rb21
-rw-r--r--app/views/admin/groups/_form.html.haml10
-rw-r--r--app/views/admin/groups/_group.html.haml4
-rw-r--r--app/views/admin/groups/edit.html.haml4
-rw-r--r--app/views/admin/groups/index.html.haml4
-rw-r--r--app/views/admin/groups/new.html.haml4
-rw-r--r--app/views/admin/groups/show.html.haml56
-rw-r--r--app/views/explore/_head.html.haml4
-rw-r--r--app/views/explore/groups/_nav.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml12
-rw-r--r--app/views/explore/projects/_filter.html.haml6
-rw-r--r--app/views/explore/projects/_nav.html.haml6
-rw-r--r--app/views/explore/projects/index.html.haml4
-rw-r--r--app/views/explore/projects/starred.html.haml4
-rw-r--r--app/views/explore/projects/trending.html.haml4
-rw-r--r--app/views/import/_githubish_status.html.haml20
-rw-r--r--app/views/import/_project_status.html.haml11
-rw-r--r--app/views/import/manifest/_form.html.haml23
-rw-r--r--app/views/import/manifest/new.html.haml12
-rw-r--r--app/views/import/manifest/status.html.haml42
-rw-r--r--app/views/projects/_import_project_pane.html.haml47
-rw-r--r--app/views/projects/new.html.haml13
-rw-r--r--changelogs/unreleased/48789-remove-event-listeners-scroll.yml6
-rw-r--r--changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml5
-rw-r--r--changelogs/unreleased/dz-manifest-import.yml5
-rw-r--r--changelogs/unreleased/fix-performance-problem-of-tags-query.yml5
-rw-r--r--changelogs/unreleased/gitaly-serverservice-info-timeout.yml5
-rw-r--r--changelogs/unreleased/ide-pipeline-icon-open.yml5
-rw-r--r--changelogs/unreleased/issue_47709.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-wiki-empty-check.yml5
-rw-r--r--changelogs/unreleased/winh-upgrade-grape-path-helpers.yml5
-rw-r--r--config/routes/import.rb6
-rw-r--r--doc/api/README.md32
-rw-r--r--doc/api/project_import_export.md28
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/development/fe_guide/img/vue_arch.pngbin9848 -> 0 bytes
-rw-r--r--doc/development/fe_guide/vue.md239
-rw-r--r--doc/user/project/clusters/eks_and_gitlab/index.md2
-rw-r--r--doc/user/project/import/img/manifest_status.pngbin0 -> 34878 bytes
-rw-r--r--doc/user/project/import/img/manifest_upload.pngbin0 -> 12079 bytes
-rw-r--r--doc/user/project/import/index.md1
-rw-r--r--doc/user/project/import/manifest.md48
-rw-r--r--doc/user/project/issue_board.md10
-rw-r--r--lib/api/entities.rb15
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/gitaly/server.rb2
-rw-r--r--lib/gitlab/git/repository.rb163
-rw-r--r--lib/gitlab/gitaly_client/server_service.rb2
-rw-r--r--lib/gitlab/import_sources.rb3
-rw-r--r--lib/gitlab/manifest_import/manifest.rb81
-rw-r--r--lib/gitlab/manifest_import/project_creator.rb41
-rw-r--r--locale/gitlab.pot126
-rw-r--r--qa/README.md2
-rw-r--r--qa/qa.rb14
-rw-r--r--qa/qa/factory/repository/project_push.rb2
-rw-r--r--qa/qa/factory/repository/push.rb16
-rw-r--r--qa/qa/factory/resource/fork.rb24
-rw-r--r--qa/qa/factory/resource/kubernetes_cluster.rb3
-rw-r--r--qa/qa/factory/resource/merge_request_from_fork.rb24
-rw-r--r--qa/qa/factory/resource/project.rb1
-rw-r--r--qa/qa/factory/resource/user.rb34
-rw-r--r--qa/qa/page/issuable/sidebar.rb7
-rw-r--r--qa/qa/page/layout/banner.rb17
-rw-r--r--qa/qa/page/main/login.rb19
-rw-r--r--qa/qa/page/main/sign_up.rb27
-rw-r--r--qa/qa/page/menu/main.rb4
-rw-r--r--qa/qa/page/merge_request/show.rb6
-rw-r--r--qa/qa/page/project/fork/new.rb17
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/show.rb9
-rw-r--r--qa/qa/runtime/namespace.rb2
-rw-r--r--qa/qa/specs/features/login/standard_spec.rb15
-rw-r--r--qa/qa/specs/features/merge_request/create_spec.rb11
-rw-r--r--qa/qa/specs/features/project/fork_project_spec.rb23
-rw-r--r--qa/qa/specs/features/repository/protected_branches_spec.rb24
-rw-r--r--spec/controllers/metrics_controller_spec.rb49
-rw-r--r--spec/features/import/manifest_import_spec.rb51
-rw-r--r--spec/features/projects/new_project_spec.rb27
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb32
-rw-r--r--spec/fixtures/aosp_manifest.xml685
-rw-r--r--spec/helpers/namespaces_helper_spec.rb10
-rw-r--r--spec/javascripts/frequent_items/store/actions_spec.js4
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js114
-rw-r--r--spec/javascripts/helpers/vuex_action_helper_spec.js141
-rw-r--r--spec/javascripts/ide/components/ide_status_bar_spec.js26
-rw-r--r--spec/javascripts/ide/mock_data.js3
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js5
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js15
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js7
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js25
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js6
-rw-r--r--spec/javascripts/test_bundle.js4
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb47
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb10
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb46
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb33
-rw-r--r--spec/models/notification_setting_spec.rb6
-rw-r--r--spec/models/project_wiki_spec.rb10
-rw-r--r--spec/models/repository_spec.rb18
119 files changed, 2292 insertions, 878 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fd4e769ecee..4a1fa39b41d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,6 +50,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
+- [Contribution Flow](#contribution-flow)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -225,24 +226,24 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone.
-### Bug Priority labels
+### Priority labels
-Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
+Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
-| Label | Meaning | Estimate time to fix | Guidance |
-|-------|-----------------|------------------------------------------------------------------|----------|
-| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com | |
-| ~P2 | High Priority | The next release | |
-| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | |
-| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented |
+| Label | Meaning | Estimate time to fix |
+|-------|-----------------|------------------------------------------------------------------|
+| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com |
+| ~P2 | High Priority | The next release |
+| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) |
+| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) |
-### Bug Severity labels
+### Severity labels
Severity labels help us clearly communicate the impact of a ~bug on users.
-| Label | Meaning | Impact of the defect | Example |
+| Label | Meaning | Impact on Functionality | Example |
|-------|-------------------|-------------------------------------------------------|---------|
| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. |
| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
@@ -251,12 +252,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
#### Severity impact guidance
-| Label | Security Impact | Availability / Performance Impact |
-|-------|---------------------------------------------------------------------|--------------------------------------------------------------|
-| ~S1 | >50% users impacted (possible company extinction level event) | |
-| ~S2 | Many users or multiple paid customers impacted (but not apocalyptic)| The issue is (almost) guaranteed to occur in the near future |
-| ~S3 | A few users or a single paid customer impacted | The issue is likely to occur in the near future |
-| ~S4 | No paid users/customer impacted, or expected impact within 30 days | The issue _may_ occur but it's not likely |
+Severity levels can be applied further depending on the facet of the impact; e.g. Affected customers, GitLab.com availability, performance and etc. The below is a guideline.
+
+| Severity | Affected Customers/Users | GitLab.com Availability | Performance Degradation |
+|----------|---------------------------------------------------------------------|----------------------------------------------------|------------------------------|
+| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | |
+| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future |
+| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future |
+| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely |
### Label for community contributors
@@ -729,6 +732,24 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
+## Contribution Flow
+
+When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
+
+When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
+
+When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
+
+Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
+
+GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
+
+GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
+
+When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
+
+When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
+
[core team]: https://about.gitlab.com/core-team/
[team]: https://about.gitlab.com/team/
[getting-help]: https://about.gitlab.com/getting-help/
diff --git a/Gemfile.lock b/Gemfile.lock
index 1b8a777ceb4..7f9207d9dfe 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -359,7 +359,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
- grape-path-helpers (1.0.5)
+ grape-path-helpers (1.0.6)
activesupport (>= 4, < 5.1)
grape (~> 1.0)
rake (~> 12)
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 0327fceb38d..1d1415fe6ca 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -41,11 +41,6 @@ export default {
required: true,
},
},
- data() {
- return {
- activeFile: '',
- };
- },
computed: {
...mapState({
isLoading: state => state.diffs.isLoading,
@@ -126,14 +121,6 @@ export default {
eventHub.$emit('fetchNotesData');
}
},
- setActive(filePath) {
- this.activeFile = filePath;
- },
- unsetActive(filePath) {
- if (this.activeFile === filePath) {
- this.activeFile = '';
- }
- },
adjustView() {
if (this.shouldShow && this.isParallelView) {
window.mrTabs.expandViewContainer();
@@ -195,7 +182,6 @@ export default {
<changed-files
:diff-files="diffFiles"
- :active-file="activeFile"
/>
<div
@@ -207,8 +193,6 @@ export default {
:key="file.newPath"
:file="file"
:current-user="currentUser"
- @setActive="setActive(file.filePath)"
- @unsetActive="unsetActive(file.filePath)"
/>
</div>
<no-changes v-else />
diff --git a/app/assets/javascripts/diffs/components/changed_files.vue b/app/assets/javascripts/diffs/components/changed_files.vue
index 9d29357d800..97751db1254 100644
--- a/app/assets/javascripts/diffs/components/changed_files.vue
+++ b/app/assets/javascripts/diffs/components/changed_files.vue
@@ -16,13 +16,6 @@ export default {
ClipboardButton,
},
mixins: [changedFilesMixin],
- props: {
- activeFile: {
- type: String,
- required: false,
- default: '',
- },
- },
data() {
return {
isStuck: false,
@@ -70,7 +63,7 @@ export default {
pluralize,
handleScroll() {
if (!this.updating) {
- requestAnimationFrame(this.updateIsStuck);
+ this.$nextTick(this.updateIsStuck);
this.updating = true;
}
},
@@ -148,25 +141,8 @@ export default {
/>
<span
- v-show="activeFile"
- class="prepend-left-5"
- >
- <strong class="prepend-right-5">
- {{ truncatedDiffPath(activeFile) }}
- </strong>
- <clipboard-button
- :text="activeFile"
- :title="s__('Copy file name to clipboard')"
- tooltip-placement="bottom"
- tooltip-container="body"
- class="btn btn-default btn-transparent btn-clipboard"
- />
- </span>
-
- <span
- v-show="!isStuck"
- id="diff-stats"
- class="diff-stats-additions-deletions-expanded"
+ class="js-diff-stats-additions-deletions-expanded
+ diff-stats-additions-deletions-expanded"
>
with
<strong class="cgreen">
@@ -177,6 +153,17 @@ export default {
{{ pluralize(`${sumRemovedLines} deletion`, sumRemovedLines) }}
</strong>
</span>
+ <div
+ class="js-diff-stats-additions-deletions-collapsed
+ diff-stats-additions-deletions-collapsed float-right d-sm-none"
+ >
+ <strong class="cgreen">
+ +{{ sumAddedLines }}
+ </strong>
+ <strong class="cred">
+ -{{ sumRemovedLines }}
+ </strong>
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
index b38d217fbe3..045688a32bf 100644
--- a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
@@ -40,7 +40,7 @@ export default {
{{ n__('%d changed file', '%d changed files', diffFiles.length) }}
</span>
<icon
- :size="8"
+ class="caret-icon"
name="chevron-down"
/>
</button>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index a61e368249a..944084f05c9 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -25,7 +25,6 @@ export default {
},
data() {
return {
- isActive: false,
isLoadingCollapsedDiff: false,
forkMessageVisible: false,
};
@@ -48,12 +47,6 @@ export default {
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
},
},
- mounted() {
- document.addEventListener('scroll', this.handleScroll);
- },
- beforeDestroy() {
- document.removeEventListener('scroll', this.handleScroll);
- },
methods: {
...mapActions('diffs', ['loadCollapsedDiff']),
handleToggle() {
@@ -65,36 +58,6 @@ export default {
this.file.collapsed = !this.file.collapsed;
}
},
- handleScroll() {
- if (!this.updating) {
- requestAnimationFrame(this.scrollUpdate.bind(this));
- this.updating = true;
- }
- },
- scrollUpdate() {
- const header = document.querySelector('.js-diff-files-changed');
- if (!header) {
- this.updating = false;
- return;
- }
-
- const { top, bottom } = this.$el.getBoundingClientRect();
- const { top: topOfFixedHeader, bottom: bottomOfFixedHeader } = header.getBoundingClientRect();
-
- const headerOverlapsContent = top < topOfFixedHeader && bottom > bottomOfFixedHeader;
- const fullyAboveHeader = bottom < bottomOfFixedHeader;
- const fullyBelowHeader = top > topOfFixedHeader;
-
- if (headerOverlapsContent && !this.isActive) {
- this.$emit('setActive');
- this.isActive = true;
- } else if (this.isActive && (fullyAboveHeader || fullyBelowHeader)) {
- this.$emit('unsetActive');
- this.isActive = false;
- }
-
- this.updating = false;
- },
handleLoadCollapsedDiff() {
this.isLoadingCollapsedDiff = true;
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 0582ad32e92..715dc1bfb42 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import { rightSidebarViews } from '../constants';
export default {
components: {
@@ -49,6 +50,7 @@ export default {
this.stopPipelinePolling();
},
methods: {
+ ...mapActions(['setRightPane']),
...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']),
startTimer() {
this.intervalId = setInterval(() => {
@@ -69,24 +71,31 @@ export default {
return `${this.currentProject.web_url}/commit/${shortSha}`;
},
},
+ rightSidebarViews,
};
</script>
<template>
<footer class="ide-status-bar">
<div
- v-if="lastCommit && lastCommitFormatedAge"
+ v-if="lastCommit"
class="ide-status-branch"
>
<span
v-if="latestPipeline && latestPipeline.details"
class="ide-status-pipeline"
>
- <ci-icon
- v-tooltip
- :status="latestPipeline.details.status"
- :title="latestPipeline.details.status.text"
- />
+ <button
+ type="button"
+ class="p-0 border-0 h-50"
+ @click="setRightPane($options.rightSidebarViews.pipelines)"
+ >
+ <ci-icon
+ v-tooltip
+ :status="latestPipeline.details.status"
+ :title="latestPipeline.details.status.text"
+ />
+ </button>
Pipeline
<a
:href="latestPipeline.details.status.details_path"
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
index cdd8076952f..6ef938b0ae2 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
@@ -31,7 +31,7 @@ export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, searc
dispatch('requestMergeRequests', type);
dispatch('resetMergeRequests', type);
- Api.mergeRequests({ scope, state, search })
+ return Api.mergeRequests({ scope, state, search })
.then(({ data }) => dispatch('receiveMergeRequestsSuccess', { type, data }))
.catch(() => dispatch('receiveMergeRequestsError', { type, search }));
};
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
index 8cb01f25223..3e67b222e66 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
@@ -102,7 +102,7 @@ export const receiveJobsSuccess = ({ commit }, { id, data }) =>
export const fetchJobs = ({ dispatch }, stage) => {
dispatch('requestJobs', stage.id);
- axios
+ return axios
.get(stage.dropdownPath)
.then(({ data }) => dispatch('receiveJobsSuccess', { id: stage.id, data }))
.catch(() => dispatch('receiveJobsError', stage));
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 55b87f3a8ec..9aff95dcfec 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -32,7 +32,7 @@
};
</script>
<template>
- <div class="space-children d-flex append-right-10">
+ <div class="space-children d-flex append-right-10 widget-status-icon">
<div
v-if="isLoading"
class="mr-widget-icon"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index fe777a07189..a5ca7b719a1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -233,7 +233,7 @@ export default {
<status-icon :status="iconClass" />
<div class="media-body">
<div class="mr-widget-body-controls media space-children">
- <span class="btn-group append-bottom-5">
+ <span class="btn-group">
<button
:disabled="isMergeButtonDisabled"
:class="mergeButtonClass"
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 7e89f8998fb..5e39bbb9890 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -518,6 +518,12 @@
outline: none;
color: $gl-link-hover-color;
}
+
+ .caret-icon {
+ position: relative;
+ top: 2px;
+ left: -1px;
+ }
}
// Mobile
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2af79c511ba..5835b8b8c9b 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -214,6 +214,10 @@
}
}
+ .widget-status-icon {
+ align-self: flex-start;
+ }
+
.mr-widget-body {
line-height: 28px;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 21cc6dfdd16..f45fcd4d900 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,7 +30,13 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, prepend: true
helper_method :can?
- helper_method :import_sources_enabled?, :github_import_enabled?, :gitea_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
+ helper_method :import_sources_enabled?, :github_import_enabled?,
+ :gitea_import_enabled?, :github_import_configured?,
+ :gitlab_import_enabled?, :gitlab_import_configured?,
+ :bitbucket_import_enabled?, :bitbucket_import_configured?,
+ :google_code_import_enabled?, :fogbugz_import_enabled?,
+ :git_import_enabled?, :gitlab_project_import_enabled?,
+ :manifest_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -351,6 +357,10 @@ class ApplicationController < ActionController::Base
Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
end
+ def manifest_import_enabled?
+ Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest')
+ end
+
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb
new file mode 100644
index 00000000000..e5a719fa0df
--- /dev/null
+++ b/app/controllers/import/manifest_controller.rb
@@ -0,0 +1,93 @@
+class Import::ManifestController < Import::BaseController
+ before_action :whitelist_query_limiting, only: [:create]
+ before_action :verify_import_enabled
+ before_action :ensure_import_vars, only: [:create, :status]
+
+ def new
+ end
+
+ def status
+ @already_added_projects = find_already_added_projects
+ already_added_import_urls = @already_added_projects.pluck(:import_url)
+
+ @pending_repositories = repositories.to_a.reject do |repository|
+ already_added_import_urls.include?(repository[:url])
+ end
+ end
+
+ def upload
+ group = Group.find(params[:group_id])
+
+ unless can?(current_user, :create_projects, group)
+ @errors = ["You don't have enough permissions to create projects in the selected group"]
+
+ render :new && return
+ end
+
+ manifest = Gitlab::ManifestImport::Manifest.new(params[:manifest].tempfile)
+
+ if manifest.valid?
+ session[:manifest_import_repositories] = manifest.projects
+ session[:manifest_import_group_id] = group.id
+
+ redirect_to status_import_manifest_path
+ else
+ @errors = manifest.errors
+
+ render :new
+ end
+ end
+
+ def jobs
+ render json: find_jobs
+ end
+
+ def create
+ repository = repositories.find do |project|
+ project[:id] == params[:repo_id].to_i
+ end
+
+ project = Gitlab::ManifestImport::ProjectCreator.new(repository, group, current_user).execute
+
+ if project.persisted?
+ render json: ProjectSerializer.new.represent(project)
+ else
+ render json: { errors: project_save_error(project) }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def ensure_import_vars
+ unless group && repositories.present?
+ redirect_to(new_import_manifest_path)
+ end
+ end
+
+ def group
+ @group ||= Group.find_by(id: session[:manifest_import_group_id])
+ end
+
+ def repositories
+ @repositories ||= session[:manifest_import_repositories]
+ end
+
+ def find_jobs
+ find_already_added_projects.to_json(only: [:id], methods: [:import_status])
+ end
+
+ def find_already_added_projects
+ group.all_projects
+ .where(import_type: 'manifest')
+ .where(creator_id: current_user)
+ .includes(:import_state)
+ end
+
+ def verify_import_enabled
+ render_404 unless manifest_import_enabled?
+ end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/48939')
+ end
+end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 9be93fa69ae..9008db1b300 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -3,7 +3,7 @@ module NamespacesHelper
params.dig(:project, :namespace_id) || params[:namespace_id]
end
- def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
+ def namespaces_options(selected = :current_user, display_path: false, extra_group: nil, groups_only: false)
groups = current_user.manageable_groups
.joins(:route)
.includes(:route)
@@ -20,10 +20,13 @@ module NamespacesHelper
options = []
options << options_for_group(groups, display_path: display_path, type: 'group')
- options << options_for_group(users, display_path: display_path, type: 'user')
- if selected == :current_user && current_user.namespace
- selected = current_user.namespace.id
+ unless groups_only
+ options << options_for_group(users, display_path: display_path, type: 'user')
+
+ if selected == :current_user && current_user.namespace
+ selected = current_user.namespace.id
+ end
end
grouped_options_for_select(options, selected)
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 9195408551f..1933c46ee44 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -32,6 +32,7 @@ class NotificationSetting < ActiveRecord::Base
:reopen_issue,
:close_issue,
:reassign_issue,
+ :issue_due,
:new_merge_request,
:push_to_merge_request,
:reopen_merge_request,
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index fc868c3ebb7..9ae2fb0013a 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -20,7 +20,6 @@ class ProjectWiki
@user = user
end
- delegate :empty?, to: :pages
delegate :repository_storage, :hashed_storage?, to: :project
def path
@@ -74,6 +73,10 @@ class ProjectWiki
!!find_page('home')
end
+ def empty?
+ pages(limit: 1).empty?
+ end
+
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages(limit: nil)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 5ed2a7b4068..a96c73e6ab7 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -174,8 +174,8 @@ class Repository
CommitCollection.new(project, commits, ref)
end
- def find_branch(name, fresh_repo: true)
- raw_repository.find_branch(name, fresh_repo)
+ def find_branch(name)
+ raw_repository.find_branch(name)
end
def find_tag(name)
diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index 5c337a9faa5..c2dfbac5414 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -1,11 +1,12 @@
module Groups
class NestedCreateService < Groups::BaseService
- attr_reader :group_path
+ attr_reader :group_path, :visibility_level
def initialize(user, params)
@current_user, @params = user, params.dup
-
@group_path = @params.delete(:group_path)
+ @visibility_level = @params.delete(:visibility_level) ||
+ Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
def execute
@@ -36,11 +37,12 @@ module Groups
new_params = params.reverse_merge(
path: subgroup_name,
name: subgroup_name,
- parent: last_group
+ parent: last_group,
+ visibility_level: visibility_level
)
- new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility
- last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute
+ last_group = namespace_or_group(partial_path) ||
+ Groups::CreateService.new(current_user, new_params).execute
end
last_group
diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb
index 51ff9eff5e4..c237d2ae8c9 100644
--- a/app/services/metrics_service.rb
+++ b/app/services/metrics_service.rb
@@ -1,35 +1,16 @@
require 'prometheus/client/formats/text'
class MetricsService
- CHECKS = [
- Gitlab::HealthChecks::DbCheck,
- Gitlab::HealthChecks::Redis::RedisCheck,
- Gitlab::HealthChecks::Redis::CacheCheck,
- Gitlab::HealthChecks::Redis::QueuesCheck,
- Gitlab::HealthChecks::Redis::SharedStateCheck,
- Gitlab::HealthChecks::GitalyCheck
- ].freeze
-
def prometheus_metrics_text
Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path)
end
- def health_metrics_text
- metrics = CHECKS.flat_map(&:metrics)
-
- formatter.marshal(metrics)
- end
-
def metrics_text
- prometheus_metrics_text.concat(health_metrics_text)
+ prometheus_metrics_text
end
private
- def formatter
- @formatter ||= Gitlab::HealthChecks::PrometheusTextFormat.new
- end
-
def multiprocess_metrics_path
::Prometheus::Client.configuration.multiprocess_files_dir
end
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index c8008771236..a3773e90cfb 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -6,7 +6,7 @@
= render_if_exists 'admin/namespace_plan', f: f
.form-group.row.group-description-holder
- = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2'
+ = f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2'
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
@@ -26,12 +26,12 @@
.alert.alert-info
= render 'shared/group_tips'
.form-actions
- = f.submit 'Create group', class: "btn btn-create"
- = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
+ = f.submit _('Create group'), class: "btn btn-create"
+ = link_to _('Cancel'), admin_groups_path, class: "btn btn-cancel"
- else
.form-actions
- = f.submit 'Save changes', class: "btn btn-save"
- = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
+ = f.submit _('Save changes'), class: "btn btn-save"
+ = link_to _('Cancel'), admin_group_path(@group), class: "btn btn-cancel"
= render_if_exists 'ldap_group_links/ldap_syncrhonizations', group: @group
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 3f96988c203..0a688b90f3a 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -3,8 +3,8 @@
%li.group-row{ class: css_class }
.controls
- = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
- = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
+ = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
+ = link_to _('Delete'), [:admin, group], data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } }, method: :delete, class: 'btn btn-remove'
.stats
%span.badge.badge-pill
= storage_counter(group.storage_size)
diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml
index c2b9807015d..8e9e1a58a17 100644
--- a/app/views/admin/groups/edit.html.haml
+++ b/app/views/admin/groups/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", @group.name, "Groups"
-%h3.page-title Edit group: #{@group.name}
+- page_title _("Edit"), @group.name, _("Groups")
+%h3.page-title= _('Edit group: %{group_name}') % { group_name: @group.name }
%hr
= render 'form', visibility_level: @group.visibility_level
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 25946ba6eaf..6a9b85b4109 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title "Groups"
+- page_title _("Groups")
%div{ class: container_class }
.top-area
@@ -13,7 +13,7 @@
= icon("search", class: "search-icon")
= render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
= link_to new_admin_group_path, class: "btn btn-new" do
- New group
+ = _('New group')
%ul.content-list
= render @groups
diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml
index 8f9fe96249f..553e8638e52 100644
--- a/app/views/admin/groups/new.html.haml
+++ b/app/views/admin/groups/new.html.haml
@@ -1,4 +1,4 @@
-- page_title "New Group"
-%h3.page-title New group
+- page_title _("New Group")
+%h3.page-title= _('New group')
%hr
= render 'form', visibility_level: default_group_visibility
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index a40f98ad24f..72b068ea6b5 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -1,61 +1,58 @@
-- add_to_breadcrumbs "Groups", admin_groups_path
+- add_to_breadcrumbs _("Groups"), admin_groups_path
- breadcrumb_title @group.name
-- page_title @group.name, "Groups"
+- page_title @group.name, _("Groups")
%h3.page-title
- Group: #{@group.full_name}
+ = _('Group: %{group_name}') % { group_name: @group.full_name }
= link_to admin_group_edit_path(@group), class: "btn float-right" do
%i.fa.fa-pencil-square-o
- Edit
+ = _('Edit')
%hr
.row
.col-md-6
.card
.card-header
- Group info:
+ = _('Group info:')
%ul.content-list
%li
.avatar-container.s60
= group_icon(@group, class: "avatar s60")
%li
- %span.light Name:
+ %span.light= _('Name:')
%strong= @group.name
%li
- %span.light Path:
+ %span.light= _('Path:')
%strong
= @group.path
%li
- %span.light Description:
+ %span.light= _('Description:')
%strong
= @group.description
%li
- %span.light Visibility level:
+ %span.light= _('Visibility level:')
%strong
= visibility_level_label(@group.visibility_level)
%li
- %span.light Created on:
+ %span.light= _('Created on:')
%strong
= @group.created_at.to_s(:medium)
= render_if_exists 'admin/namespace_plan_info', namespace: @group
%li
- %span.light Storage:
- %strong= storage_counter(@group.storage_size)
- (
- = storage_counter(@group.repository_size)
- repositories,
- = storage_counter(@group.build_artifacts_size)
- build artifacts,
- = storage_counter(@group.lfs_objects_size)
- LFS
- )
+ %span.light= _('Storage:')
+ - counter_storage = storage_counter(@group.storage_size)
+ - counter_repositories = storage_counter(@group.repository_size)
+ - counter_build_artifacts = storage_counter(@group.build_artifacts_size)
+ - counter_lfs_objects = storage_counter(@group.lfs_objects_size)
+ %strong
+ = _("%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)") % { counter_storage: counter_storage, counter_repositories: counter_repositories, counter_build_artifacts: counter_build_artifacts, counter_lfs_objects: counter_lfs_objects }
%li
- %span.light Group Git LFS status:
+ %span.light= _('Group Git LFS status:')
%strong
= group_lfs_status(@group)
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
@@ -67,7 +64,7 @@
.card
.card-header
%h3.card-title
- Projects
+ = _('Projects')
%span.badge.badge-pill
#{@group.projects.count}
%ul.content-list
@@ -85,7 +82,7 @@
- if @group.shared_projects.any?
.card
.card-header
- Projects shared with #{@group.name}
+ = _('Projects shared with %{group_name}') % { group_name: @group.name }
%span.badge.badge-pill
#{@group.shared_projects.count}
%ul.content-list
@@ -102,11 +99,11 @@
- if can?(current_user, :admin_group_member, @group)
.card
.card-header
- Add user(s) to the group:
+ = _('Add user(s) to the group:')
.card-body.form-holder
%p.light
- Read more about project permissions
- %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
+ - link_to_help = link_to(_("here"), help_page_path("user/permissions"), class: "vlink")
+ = _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help }
= form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
@@ -114,16 +111,15 @@
.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
- = button_tag 'Add users to group', class: "btn btn-create"
+ = button_tag _('Add users to group'), class: "btn btn-create"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
.card
.card-header
- %strong= @group.name
- group members
+ = _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name }
%span.badge.badge-pill= @group.members.size
.float-right
- = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm"
%ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/explore/_head.html.haml b/app/views/explore/_head.html.haml
index a3b0709e261..eefc797cf03 100644
--- a/app/views/explore/_head.html.haml
+++ b/app/views/explore/_head.html.haml
@@ -1,6 +1,6 @@
.explore-title.text-center
%h2
- Explore GitLab
+ = _("Explore GitLab")
%p.lead
- Discover projects, groups and snippets. Share your projects with others
+ = _("Discover projects, groups and snippets. Share your projects with others")
%br
diff --git a/app/views/explore/groups/_nav.html.haml b/app/views/explore/groups/_nav.html.haml
index ab4787c6d05..c337149a2f3 100644
--- a/app/views/explore/groups/_nav.html.haml
+++ b/app/views/explore/groups/_nav.html.haml
@@ -2,7 +2,7 @@
%ul.nav-links.nav.nav-tabs
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path do
- Explore Groups
+ = _("Explore Groups")
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 0643b9cfbc5..387c37b7a91 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Groups"
-- header_title "Groups", dashboard_groups_path
+- page_title _("Groups")
+- header_title _("Groups"), dashboard_groups_path
- if current_user
= render 'dashboard/groups_head'
@@ -10,14 +10,14 @@
- if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hide
- %button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times')
+ %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times')
.svg-container
= custom_icon('icon_explore_groups_splash')
.inner-content
- %p Below you will find all the groups that are public.
- %p You can easily contribute to them by requesting to join these groups.
+ %p= _("Below you will find all the groups that are public.")
+ %p= _("You can easily contribute to them by requesting to join these groups.")
- if params[:filter].blank? && @groups.empty?
- .nothing-here-block No public groups
+ .nothing-here-block= _("No public groups")
- else
= render 'groups'
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 6abb56ba6d2..b694103ccaf 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -2,16 +2,16 @@
.dropdown
%button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' }
= icon('globe')
- %span.light Visibility:
+ %span.light= _("Visibility:")
- if params[:visibility_level].present?
= visibility_level_label(params[:visibility_level].to_i)
- else
- Any
+ = _('Any')
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right
%li
= link_to filter_projects_path(visibility_level: nil) do
- Any
+ = _('Any')
- Gitlab::VisibilityLevel.values.each do |level|
%li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' }
= link_to filter_projects_path(visibility_level: level) do
diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml
index 558cd26f1e0..bf65c19b720 100644
--- a/app/views/explore/projects/_nav.html.haml
+++ b/app/views/explore/projects/_nav.html.haml
@@ -2,13 +2,13 @@
%ul.nav-links.nav.nav-tabs
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
- Trending
+ = _('Trending')
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
- Most stars
+ = _('Most stars')
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
- All
+ = _('All')
.nav-controls
- unless current_user
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index f00802e0af7..452f390695c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Projects"
-- header_title "Projects", dashboard_projects_path
+- page_title _("Projects")
+- header_title _("Projects"), dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index f00802e0af7..452f390695c 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Projects"
-- header_title "Projects", dashboard_projects_path
+- page_title _("Projects")
+- header_title _("Projects"), dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index f00802e0af7..452f390695c 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,6 +1,6 @@
- @hide_top_links = true
-- page_title "Projects"
-- header_title "Projects", dashboard_projects_path
+- page_title _("Projects")
+- header_title _("Projects"), dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index 5e7be5cd37b..f0d1e837317 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -21,23 +21,13 @@
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
+ %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) }
%td
= provider_project_link(provider, project.import_source)
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- = _('Done')
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- = _('Started')
- - elsif project.import_status == 'failed'
- = _('Failed')
- - else
- = project.human_import_status_name
+ = render 'import/project_status', project: project
- @repos.each do |repo|
%tr{ id: "repo_#{repo.id}", data: { qa: { repo_path: repo.full_name } } }
@@ -61,6 +51,6 @@
= has_ci_cd_only_params? ? _('Connect') : _('Import')
= icon("spinner spin", class: "loading-icon")
-.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}",
- import_path: "#{url_for([:import, provider])}",
- ci_cd_only: "#{has_ci_cd_only_params?}" } }
+.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]),
+ import_path: url_for([:import, provider]),
+ ci_cd_only: has_ci_cd_only_params?.to_s } }
diff --git a/app/views/import/_project_status.html.haml b/app/views/import/_project_status.html.haml
new file mode 100644
index 00000000000..280bcbc1e63
--- /dev/null
+++ b/app/views/import/_project_status.html.haml
@@ -0,0 +1,11 @@
+- case project.import_status
+- when 'finished'
+ = icon('check')
+ = _('Done')
+- when 'started'
+ = icon("spinner spin")
+ = _('Started')
+- when 'failed'
+ = _('Failed')
+- else
+ = project.human_import_status_name
diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml
new file mode 100644
index 00000000000..763beb5958f
--- /dev/null
+++ b/app/views/import/manifest/_form.html.haml
@@ -0,0 +1,23 @@
+= form_tag upload_import_manifest_path, multipart: true do
+ .form-group
+ = label_tag :group_id, nil, class: 'label-light' do
+ = _('Group')
+ .input-group
+ .input-group-prepend.has-tooltip{ title: root_url }
+ .input-group-text
+ = root_url
+ = select_tag :group_id, namespaces_options(nil, display_path: true, groups_only: true), { class: 'select2 js-select-namespace' }
+ .form-text.text-muted
+ = _('Choose the top-level group for your repository imports.')
+
+ .form-group
+ = label_tag :manifest, class: 'label-light' do
+ = _('Manifest')
+ = file_field_tag :manifest, class: 'form-control-file', required: true
+ .form-text.text-muted
+ = _('Import multiple repositories by uploading a manifest file.')
+ = link_to icon('question-circle'), help_page_path('user/project/import/manifest')
+
+ .append-bottom-10
+ = submit_tag _('List available repositories'), class: 'btn btn-success'
+ = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel'
diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml
new file mode 100644
index 00000000000..056e4922b9e
--- /dev/null
+++ b/app/views/import/manifest/new.html.haml
@@ -0,0 +1,12 @@
+- page_title "Manifest file import"
+- header_title "Projects", root_path
+
+%h3.page-title
+ = _('Manifest file import')
+
+- if @errors.present?
+ .alert.alert-danger
+ - @errors.each do |error|
+ = error
+
+= render 'form'
diff --git a/app/views/import/manifest/status.html.haml b/app/views/import/manifest/status.html.haml
new file mode 100644
index 00000000000..5b2e1005398
--- /dev/null
+++ b/app/views/import/manifest/status.html.haml
@@ -0,0 +1,42 @@
+- page_title "Manifest import"
+- header_title "Projects", root_path
+- provider = 'manifest'
+
+%h3.page-title
+ = _('Manifest file import')
+
+%p
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ = import_all_githubish_repositories_button_label
+ = icon("spinner spin", class: "loading-icon")
+
+.table-responsive
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th= _('Repository URL')
+ %th= _('To GitLab')
+ %th= _('Status')
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) }
+ %td
+ = project.import_url
+ %td
+ = link_to_project project
+ %td.job-status
+ = render 'import/project_status', project: project
+
+ - @pending_repositories.each do |repository|
+ %tr{ id: "repo_#{repository[:id]}" }
+ %td
+ = repository[:url]
+ %td.import-target
+ = import_project_target(@group.full_path, repository[:path])
+ %td.import-actions.job-status
+ = button_tag class: "btn btn-import js-add-to-import" do
+ = _('Import')
+ = icon("spinner spin", class: "loading-icon")
+
+.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]),
+ import_path: url_for([:import, provider]) } }
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 8f535b9d789..3da6db08580 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -1,49 +1,62 @@
- active_tab = local_assigns.fetch(:active_tab, 'blank')
-- f = local_assigns.fetch(:f)
.project-import
.form-group.import-btn-container.clearfix
- = f.label :visibility_level, class: 'label-light' do #the label here seems wrong
+ %h5
Import project from
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
- %div
- - if github_import_enabled?
+
+ - if github_import_enabled?
+ %div
= link_to new_import_github_path, class: 'btn js-import-github' do
= icon('github', text: 'GitHub')
- %div
- - if bitbucket_import_enabled?
+
+ - if bitbucket_import_enabled?
+ %div
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
- %div
- - if gitlab_import_enabled?
+
+ - if gitlab_import_enabled?
+ %div
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
- %div
- - if google_code_import_enabled?
+
+ - if google_code_import_enabled?
+ %div
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
- %div
- - if fogbugz_import_enabled?
+
+ - if fogbugz_import_enabled?
+ %div
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
- %div
- - if gitea_import_enabled?
+
+ - if gitea_import_enabled?
+ %div
= link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
- %div
- - if git_import_enabled?
+
+ - if git_import_enabled?
+ %div
%button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
= icon('git', text: 'Repo by URL')
+
+ - if manifest_import_enabled?
+ %div
+ = link_to new_import_manifest_path, class: 'btn import_manifest' do
+ = icon('file-text-o', text: 'Manifest file')
+
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
- %hr
+ = form_for @project, html: { class: 'new_project' } do |f|
+ %hr
= render "shared/import_form", f: f
= render 'new_project_fields', f: f, project_name_id: "import-url-name"
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 5bb1bfb7059..6c363345e38 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -55,13 +55,12 @@
= render 'project_templates', f: f
.tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' }
- = form_for @project, html: { class: 'new_project' } do |f|
- - if import_sources_enabled?
- = render 'import_project_pane', f: f, active_tab: active_tab
- - else
- .nothing-here-block
- %h4 No import options available
- %p Contact an administrator to enable options for importing your project.
+ - if import_sources_enabled?
+ = render 'import_project_pane', active_tab: active_tab
+ - else
+ .nothing-here-block
+ %h4 No import options available
+ %p Contact an administrator to enable options for importing your project.
.save-project-loader.d-none
.center
diff --git a/changelogs/unreleased/48789-remove-event-listeners-scroll.yml b/changelogs/unreleased/48789-remove-event-listeners-scroll.yml
new file mode 100644
index 00000000000..9cc3f7adc36
--- /dev/null
+++ b/changelogs/unreleased/48789-remove-event-listeners-scroll.yml
@@ -0,0 +1,6 @@
+---
+title: Improves performance on Merge Request diff tab by removing the scroll event
+ listeners being added to every file
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml b/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml
new file mode 100644
index 00000000000..4942688d00f
--- /dev/null
+++ b/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml
@@ -0,0 +1,5 @@
+---
+title: Remove healthchecks from prometheus endpoint
+merge_request: 20565
+author:
+type: fixed
diff --git a/changelogs/unreleased/dz-manifest-import.yml b/changelogs/unreleased/dz-manifest-import.yml
new file mode 100644
index 00000000000..b0d29b0869f
--- /dev/null
+++ b/changelogs/unreleased/dz-manifest-import.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to import multiple repositories by uploading a manifest file
+merge_request: 20304
+author:
+type: added
diff --git a/changelogs/unreleased/fix-performance-problem-of-tags-query.yml b/changelogs/unreleased/fix-performance-problem-of-tags-query.yml
new file mode 100644
index 00000000000..4649775be9c
--- /dev/null
+++ b/changelogs/unreleased/fix-performance-problem-of-tags-query.yml
@@ -0,0 +1,5 @@
+---
+title: Fix performance problem of accessing tag list for projects api endpoints
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/gitaly-serverservice-info-timeout.yml b/changelogs/unreleased/gitaly-serverservice-info-timeout.yml
new file mode 100644
index 00000000000..7f2fe8b9c93
--- /dev/null
+++ b/changelogs/unreleased/gitaly-serverservice-info-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Use appropriate timeout on Gitaly server info checks, avoid error on timeout
+merge_request: 20552
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-pipeline-icon-open.yml b/changelogs/unreleased/ide-pipeline-icon-open.yml
new file mode 100644
index 00000000000..3a73ff2170f
--- /dev/null
+++ b/changelogs/unreleased/ide-pipeline-icon-open.yml
@@ -0,0 +1,5 @@
+---
+title: Clicking CI icon in Web IDE now opens up pipelines panel
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/issue_47709.yml b/changelogs/unreleased/issue_47709.yml
new file mode 100644
index 00000000000..c3ef55fd692
--- /dev/null
+++ b/changelogs/unreleased/issue_47709.yml
@@ -0,0 +1,5 @@
+---
+title: 'Allow to toggle notifications for issues due soon'
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-optimize-wiki-empty-check.yml b/changelogs/unreleased/sh-optimize-wiki-empty-check.yml
new file mode 100644
index 00000000000..31ca7497b5a
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-wiki-empty-check.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize ProjectWiki#empty? check
+merge_request: 20573
+author:
+type: performance
diff --git a/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml b/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml
new file mode 100644
index 00000000000..62addff1d0f
--- /dev/null
+++ b/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade grape-path-helpers to 1.0.6
+merge_request: 20601
+author:
+type: other
diff --git a/config/routes/import.rb b/config/routes/import.rb
index c378253bf15..efd0260ff60 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -45,4 +45,10 @@ namespace :import do
resource :gitlab_project, only: [:create, :new] do
post :create
end
+
+ resource :manifest, only: [:create, :new], controller: :manifest do
+ get :status
+ get :jobs
+ post :upload
+ end
end
diff --git a/doc/api/README.md b/doc/api/README.md
index 6267618d3bc..4566319ad45 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -388,7 +388,7 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/diaspora%2Fdiaspora
```
-## Branches & tags name encoding
+## Branches and tags name encoding
If your branch or tag contains a `/`, make sure the branch/tag name is
URL-encoded.
@@ -399,6 +399,36 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/1/branches/my%2Fbranch/commits
```
+## Encoding API parameters of `array` and `hash` types
+
+When making an API call with parameters of type `array` and/or `hash`, the parameters may be
+specified as shown below.
+
+### `array`
+
+`import_sources` is a parameter of type `array`:
+
+```
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
+-d "import_sources[]=github" \
+-d "import_sources[]=bitbucket" \
+"https://gitlab.example.com/api/v4/some_endpoint
+```
+
+### `hash`
+
+`override_params` is a parameter of type `hash`:
+
+```
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
+--form "namespace=email" \
+--form "path=impapi" \
+--form "file=@/path/to/somefile.txt"
+--form "override_params[visibility]=private" \
+--form "override_params[some_other_param]=some_value" \
+https://gitlab.example.com/api/v4/projects/import
+```
+
## `id` vs `iid`
When you work with the API, you may notice two similar fields in API entities:
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index 085437c801a..83e405141f1 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -28,8 +28,11 @@ POST /projects/:id/export
| `upload[url]` | string | yes | The URL to upload the project |
| `upload[http_method]` | string | no | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` |
+
```console
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export --data "description=FooBar&upload[http_method]=PUT&upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export \
+ --data "upload[http_method]=PUT" \
+ --data-urlencode "upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
```
```json
@@ -125,6 +128,29 @@ by `@`. For example:
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
```
+cURL doesn't support posting a file from a remote server. Importing a project from a remote server can be accomplished through something like the following:
+
+```python
+import requests
+import urllib
+import json
+import sys
+
+s3_file = urllib.urlopen(presigned_url)
+
+url = 'https://gitlab.example.com/api/v4/projects/import'
+files = {'file': s3_file}
+data = {
+ "path": "example-project",
+ "namespace": "example-group"
+}
+headers = {
+ 'Private-Token': "9koXpg98eAheJpvBs5tK"
+}
+
+requests.post(url, headers=headers, data=data, files=files)
+```
+
```json
{
"id": 1,
diff --git a/doc/api/settings.md b/doc/api/settings.md
index e6b207d8746..b6f2101fc7b 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -105,7 +105,7 @@ PUT /application/settings
| `housekeeping_gc_period` | integer | no | Number of Git pushes after which 'git gc' is run. |
| `housekeeping_incremental_repack_period` | integer | no | Number of Git pushes after which an incremental 'git repack' is run. |
| `html_emails_enabled` | boolean | no | Enable HTML emails |
-| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project |
+| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project manifest |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
diff --git a/doc/development/fe_guide/img/vue_arch.png b/doc/development/fe_guide/img/vue_arch.png
deleted file mode 100644
index a67706c7c1e..00000000000
--- a/doc/development/fe_guide/img/vue_arch.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 219b98ac696..f6cbd11042c 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -2,27 +2,24 @@
To get started with Vue, read through [their documentation][vue-docs].
-## Vue architecture
+## Examples
-All new features built with Vue.js must follow a [Flux architecture][flux].
-The main goal we are trying to achieve is to have only one data flow and only one data entry.
-In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below:
+What is described in the following sections can be found in these examples:
-Each Vue bundle needs a Store - where we keep all the data -, a Service - that we use to communicate with the server - and a main Vue component.
+- web ide: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores
+- security products: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports
+- registry: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores
-Think of the Main Vue Component as the entry point of your application. This is the only smart
-component that should exist in each Vue feature.
-This component is responsible for:
-1. Calling the Service to get data from the server
-1. Calling the Store to store the data received
-1. Mounting all the other components
+## Vue architecture
-![Vue Architecture](img/vue_arch.png)
+All new features built with Vue.js must follow a [Flux architecture][flux].
+The main goal we are trying to achieve is to have only one data flow and only one data entry.
+In order to achieve this goal we use [vuex](#vuex).
You can also read about this architecture in vue docs about [state management][state-management]
and about [one way data flow][one-way-data-flow].
-### Components, Stores and Services
+### Components and Store
In some features implemented with Vue.js, like the [issue board][issue-boards]
or [environments table][environments-table]
@@ -33,10 +30,8 @@ new_feature
├── components
│ └── component.vue
│ └── ...
-├── stores
+├── store
│ └── new_feature_store.js
-├── services # only when not using vuex
-│ └── new_feature_service.js
├── index.js
```
_For consistency purposes, we recommend you to follow the same structure._
@@ -125,217 +120,6 @@ You can read more about components in Vue.js site, [Component System][component-
#### Vuex
Check this [page](vuex.md) for more details.
-#### Flux like state management
-The Store is a class that allows us to manage the state in a single
-source of truth. It is not aware of the service or the components.
-
-The concept we are trying to follow is better explained by Vue documentation
-itself, please read this guide: [State Management][state-management]
-
-### A folder for the Service
-
-**If you are using Vuex you won't need this step**
-
-The Service is a class used only to communicate with the server.
-It does not store or manipulate any data. It is not aware of the store or the components.
-We use [axios][axios] to communicate with the server.
-Refer to [axios](axios.md) for more details.
-
-Axios instance should only be imported in the service file.
-
-```javascript
-import axios from '~/lib/utils/axios_utils';
-```
-
-### End Result
-
-The following example shows an application:
-
-```javascript
-// store.js
-export default class Store {
-
- /**
- * This is where we will iniatialize the state of our data.
- * Usually in a small SPA you don't need any options when starting the store.
- * In that case you do need guarantee it's an Object and it's documented.
- *
- * @param {Object} options
- */
- constructor(options) {
- this.options = options;
-
- // Create a state object to handle all our data in the same place
- this.todos = [];
- }
-
- setTodos(todos = []) {
- this.todos = todos;
- }
-
- addTodo(todo) {
- this.todos.push(todo);
- }
-
- removeTodo(todoID) {
- const state = this.todos;
-
- const newState = state.filter((element) => {element.id !== todoID});
-
- this.todos = newState;
- }
-}
-
-// service.js
-import axios from '~/lib/utils/axios_utils'
-
-export default class Service {
- constructor(options) {
- this.todos = axios.create({
- baseURL: endpoint.todosEndpoint
- });
-
- }
-
- getTodos() {
- return this.todos.get();
- }
-
- addTodo(todo) {
- return this.todos.put(todo);
- }
-}
-// todo_component.vue
-<script>
-export default {
- props: {
- data: {
- type: Object,
- required: true,
- },
- },
-};
-</script>
-<template>
- <div>
- <h1>
- Title: {{data.title}}
- </h1>
- <p>
- {{data.text}}
- </p>
- </div>
-</template>
-
-// todos_main_component.vue
-<script>
-import Store from 'store';
-import Service from 'service';
-import TodoComponent from 'todoComponent';
-export default {
- components: {
- todo: TodoComponent,
- },
- /**
- * Although most data belongs in the store, each component it's own state.
- * We want to show a loading spinner while we are fetching the todos, this state belong
- * in the component.
- *
- * We need to access the store methods through all methods of our component.
- * We need to access the state of our store.
- */
- data() {
- const store = new Store();
-
- return {
- isLoading: false,
- store: store,
- todos: store.todos,
- };
- },
-
- created() {
- this.service = new Service('/todos');
-
- this.getTodos();
- },
-
- methods: {
- getTodos() {
- this.isLoading = true;
-
- this.service
- .getTodos()
- .then(response => {
- this.store.setTodos(response);
- this.isLoading = false;
- })
- .catch(() => {
- this.isLoading = false;
- // Show an error
- });
- },
-
- addTodo(event) {
- this.service
- .addTodo({
- title: 'New entry',
- text: `You clicked on ${event.target.tagName}`,
- })
- .then(response => {
- this.store.addTodo(response);
- })
- .catch(() => {
- // Show an error
- });
- },
- },
-};
-</script>
-<template>
- <div class="container">
- <div v-if="isLoading">
- <i
- class="fa fa-spin fa-spinner"
- aria-hidden="true" />
- </div>
-
- <div
- v-if="!isLoading"
- class="js-todo-list">
- <template v-for='todo in todos'>
- <todo :data="todo" />
- </template>
-
- <button
- @click="addTodo"
- class="js-add-todo">
- Add Todo
- </button>
- </div>
- <div>
-</template>
-
-// index.js
-import todoComponent from 'todos_main_component.vue';
-
-new Vue({
- el: '.js-todo-app',
- components: {
- todoComponent,
- },
- render: createElement => createElement('todo-component' {
- props: {
- someProp: [],
- }
- }),
-});
-
-```
-
-The [issue boards service][issue-boards-service]
-is a good example of this pattern.
-
## Style guide
Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js)
@@ -446,6 +230,5 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
[vue-test]: https://vuejs.org/v2/guide/unit-testing.html
-[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
[flux]: https://facebook.github.io/flux
[axios]: https://github.com/axios/axios
diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md
index 2d8fdf0d1da..ec8467da14f 100644
--- a/doc/user/project/clusters/eks_and_gitlab/index.md
+++ b/doc/user/project/clusters/eks_and_gitlab/index.md
@@ -62,7 +62,7 @@ Click on `Add Kubernetes cluster`, the cluster is now connected to GitLab. At th
If you would like to utilize your own CI/CD scripts to deploy to the cluster, you can stop here.
-## Disable Role Based-Access Control (RBAC)
+## Disable Role-Based Access Control (RBAC)
Presently, Auto DevOps and one-click app installs do not support [Kubernetes role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/). Support is [being worked on](https://gitlab.com/groups/gitlab-org/-/epics/136), but in the interim RBAC must be disabled to utilize for these features.
diff --git a/doc/user/project/import/img/manifest_status.png b/doc/user/project/import/img/manifest_status.png
new file mode 100644
index 00000000000..b706116a2ac
--- /dev/null
+++ b/doc/user/project/import/img/manifest_status.png
Binary files differ
diff --git a/doc/user/project/import/img/manifest_upload.png b/doc/user/project/import/img/manifest_upload.png
new file mode 100644
index 00000000000..d6bf4b157dd
--- /dev/null
+++ b/doc/user/project/import/img/manifest_upload.png
Binary files differ
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 72cc58546b7..b55435e5b4f 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -11,6 +11,7 @@
1. [From SVN](svn.md)
1. [From TFS](tfs.md)
1. [From repo by URL](repo_by_url.md)
+1. [By uploading a manifest file](manifest.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md
new file mode 100644
index 00000000000..812ecf05faf
--- /dev/null
+++ b/doc/user/project/import/manifest.md
@@ -0,0 +1,48 @@
+# Import multiple repositories by uploading a manifest file
+
+GitLab allows you to import all the required git repositories
+based a manifest file like the one used by the Android repository.
+
+
+>**Note:**
+This feature requires [subgroups](../../group/subgroups/index.md) to be supported by your database.
+
+You can do it by following next steps:
+
+1. From your GitLab dashboard click **New project**
+1. Switch to the **Import project** tab
+1. Click on the **Manifest file** button
+1. Provide GitLab with a manifest xml file
+1. Select a group you want to import to (you need to create a group first if you don't have one)
+1. Click **List available repositories**
+1. You will be redirected to the import status page with projects list based on manifest file
+1. Check the list and click 'Import all repositories' to start import.
+
+![Manifest upload](img/manifest_upload.png)
+
+![Manifest status](img/manifest_status.png)
+
+### Manifest format
+
+A manifest must be an XML file. There must be one `remote` tag with `review` attribute
+that contains a URL to a git server. Each `project` tag must have `name` and `path` attribute.
+GitLab will build URL to the repository by combining URL from `remote` tag with a project name.
+A path attribute will be used to represent project path in GitLab system.
+
+Below is a valid example of manifest file.
+
+```xml
+<manifest>
+ <remote review="https://android-review.googlesource.com/" />
+
+ <project path="build/make" name="platform/build" />
+ <project path="build/blueprint" name="platform/build/blueprint" />
+</manifest>
+```
+
+As result next projects will be created:
+
+| GitLab | Import URL |
+|---|---|
+| https://gitlab/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build |
+| https://gitlab/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint |
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index e97b5d05529..860edb8e6f7 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -364,12 +364,12 @@ When dragging issues between lists, different behavior occurs depending on the s
Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
-| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Project Issue Boards | Configurable Group Issue Boards | Assignee Lists
+| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Issue Boards | Assignee Lists
| --- | --- | --- | --- | --- | --- |
-| Core | 1 | 1 | No | No | No |
-| Starter | Multiple | 1 | Yes | No | No |
-| Premium | Multiple | Multiple | Yes | Yes | Yes |
-| Ultimate | Multiple | Multiple | Yes | Yes | Yes |
+| Core | 1 | 1 | No | No |
+| Starter | Multiple | 1 | Yes | No |
+| Premium | Multiple | Multiple | Yes | Yes |
+| Ultimate | Multiple | Multiple | Yes | Yes |
## Tips
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 66b62d9ee2e..b256c33c631 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -135,10 +135,13 @@ module API
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
def self.preload_relation(projects_relation, options = {})
+ # Preloading tags, should be done with using only `:tags`,
+ # as `:tags` are defined as: `has_many :tags, through: :taggings`
+ # N+1 is solved then by using `subject.tags.map(&:name)`
+ # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
projects_relation.preload(:project_feature, :route)
- .preload(:import_state)
- .preload(namespace: [:route, :owner],
- tags: :taggings)
+ .preload(:import_state, :tags)
+ .preload(namespace: [:route, :owner])
end
end
@@ -212,11 +215,15 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
def self.preload_relation(projects_relation, options = {})
+ # Preloading tags, should be done with using only `:tags`,
+ # as `:tags` are defined as: `has_many :tags, through: :taggings`
+ # N+1 is solved then by using `subject.tags.map(&:name)`
+ # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
.preload(project_group_links: :group,
fork_network: :root_project,
forked_project_link: :forked_from_project,
- forked_from_project: [:route, :forks, namespace: :route, tags: :taggings])
+ forked_from_project: [:route, :forks, :tags, namespace: :route])
end
def self.forks_counting_projects(projects_relation)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 02ef89f997f..1ca7d23203b 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -25,7 +25,7 @@ module API
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
- optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
+ optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest],
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb
index 2760211fee8..f95e423ef22 100644
--- a/lib/gitaly/server.rb
+++ b/lib/gitaly/server.rb
@@ -50,7 +50,7 @@ module Gitaly
@info ||=
begin
Gitlab::GitalyClient::ServerService.new(@storage).info
- rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded
+ rescue GRPC::Unavailable, GRPC::DeadlineExceeded
# This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: [])
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d50ac2707f0..fc4711751b1 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -167,24 +167,9 @@ module Gitlab
# Directly find a branch with a simple name (e.g. master)
#
- # force_reload causes a new Rugged repository to be instantiated
- #
- # This is to work around a bug in libgit2 that causes in-memory refs to
- # be stale/invalid when packed-refs is changed.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
- def find_branch(name, force_reload = false)
- gitaly_migrate(:find_branch) do |is_enabled|
- if is_enabled
- gitaly_ref_client.find_branch(name)
- else
- reload_rugged if force_reload
-
- rugged_ref = rugged.branches[name]
- if rugged_ref
- target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
- end
- end
+ def find_branch(name)
+ wrapped_gitaly_errors do
+ gitaly_ref_client.find_branch(name)
end
end
@@ -196,20 +181,8 @@ module Gitlab
# Returns the number of valid branches
def branch_count
- gitaly_migrate(:branch_names) do |is_enabled|
- if is_enabled
- gitaly_ref_client.count_branch_names
- else
- rugged.branches.each(:local).count do |ref|
- begin
- ref.name && ref.target # ensures the branch is valid
-
- true
- rescue Rugged::ReferenceError
- false
- end
- end
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.count_branch_names
end
end
@@ -232,12 +205,8 @@ module Gitlab
# Returns the number of valid tags
def tag_count
- gitaly_migrate(:tag_names) do |is_enabled|
- if is_enabled
- gitaly_ref_client.count_tag_names
- else
- rugged.tags.count
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.count_tag_names
end
end
@@ -260,13 +229,8 @@ module Gitlab
#
# Ref names must start with `refs/`.
def ref_exists?(ref_name)
- gitaly_migrate(:ref_exists,
- status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_ref_exists?(ref_name)
- else
- rugged_ref_exists?(ref_name)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_exists?(ref_name)
end
end
@@ -274,12 +238,8 @@ module Gitlab
#
# name - The name of the tag as a String.
def tag_exists?(name)
- gitaly_migrate(:ref_exists_tags, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_ref_exists?("refs/tags/#{name}")
- else
- rugged_tag_exists?(name)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_exists?("refs/tags/#{name}")
end
end
@@ -287,12 +247,8 @@ module Gitlab
#
# name - The name of the branch as a String.
def branch_exists?(name)
- gitaly_migrate(:ref_exists_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_ref_exists?("refs/heads/#{name}")
- else
- rugged_branch_exists?(name)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_exists?("refs/heads/#{name}")
end
end
@@ -310,12 +266,8 @@ module Gitlab
end
def delete_all_refs_except(prefixes)
- gitaly_migrate(:ref_delete_refs) do |is_enabled|
- if is_enabled
- gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
- else
- delete_refs(*all_ref_names_except(prefixes))
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
end
end
@@ -714,25 +666,16 @@ module Gitlab
# Delete the specified branch from the repository
def delete_branch(branch_name)
- gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_ref_client.delete_branch(branch_name)
- else
- rugged.branches.delete(branch_name)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.delete_branch(branch_name)
end
- rescue Rugged::ReferenceError, CommandError => e
+ rescue CommandError => e
raise DeleteBranchError, e
end
def delete_refs(*ref_names)
- gitaly_migrate(:delete_refs,
- status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_delete_refs(*ref_names)
- else
- git_delete_refs(*ref_names)
- end
+ wrapped_gitaly_errors do
+ gitaly_delete_refs(*ref_names)
end
end
@@ -742,12 +685,8 @@ module Gitlab
# create_branch("feature")
# create_branch("other-feature", "master")
def create_branch(ref, start_point = "HEAD")
- gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_ref_client.create_branch(ref, start_point)
- else
- rugged_create_branch(ref, start_point)
- end
+ wrapped_gitaly_errors do
+ gitaly_ref_client.create_branch(ref, start_point)
end
end
@@ -1175,7 +1114,7 @@ module Gitlab
end
def can_be_merged?(source_sha, target_branch)
- if target_sha = find_branch(target_branch, true)&.target
+ if target_sha = find_branch(target_branch)&.target
!gitaly_conflicts_client(source_sha, target_sha).conflicts?
else
false
@@ -1549,52 +1488,10 @@ module Gitlab
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
- def rugged_ref_exists?(ref_name)
- raise ArgumentError, 'invalid refname' unless ref_name.start_with?('refs/')
-
- rugged.references.exist?(ref_name)
- rescue Rugged::ReferenceError
- false
- end
-
- # Returns true if the given ref name exists
- #
- # Ref names must start with `refs/`.
def gitaly_ref_exists?(ref_name)
gitaly_ref_client.ref_exists?(ref_name)
end
- # Returns true if the given tag exists
- #
- # name - The name of the tag as a String.
- def rugged_tag_exists?(name)
- !!rugged.tags[name]
- end
-
- # Returns true if the given branch exists
- #
- # name - The name of the branch as a String.
- def rugged_branch_exists?(name)
- rugged.branches.exists?(name)
-
- # If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
- # Whatever code calls this method shouldn't have to deal with that so
- # instead we just return `false` (which is true since a branch doesn't
- # exist when it has an invalid name).
- rescue Rugged::ReferenceError
- false
- end
-
- def rugged_create_branch(ref, start_point)
- rugged_ref = rugged.branches.create(ref, start_point)
- target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
- rescue Rugged::ReferenceError => e
- raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'}
-
- raise InvalidRef.new("Invalid reference #{start_point}")
- end
-
def gitaly_copy_gitattributes(revision)
gitaly_repository_client.apply_gitattributes(revision)
end
@@ -1687,20 +1584,6 @@ module Gitlab
remote_update(remote_name, url: url)
end
- def git_delete_refs(*ref_names)
- instructions = ref_names.map do |ref|
- "delete #{ref}\x00\x00"
- end
-
- message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
- stdin.write(instructions.join)
- end
-
- unless status.zero?
- raise GitError.new("Could not delete refs #{ref_names}: #{message}")
- end
- end
-
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
diff --git a/lib/gitlab/gitaly_client/server_service.rb b/lib/gitlab/gitaly_client/server_service.rb
index 2e1076d1f66..ad898278353 100644
--- a/lib/gitlab/gitaly_client/server_service.rb
+++ b/lib/gitlab/gitaly_client/server_service.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def info
- GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new)
+ GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout)
end
end
end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 60d5fa4d29a..af9b880ef9e 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -16,7 +16,8 @@ module Gitlab
ImportSource.new('fogbugz', 'FogBugz', Gitlab::FogbugzImport::Importer),
ImportSource.new('git', 'Repo by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
- ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer)
+ ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer),
+ ImportSource.new('manifest', 'Manifest file', nil)
].freeze
class << self
diff --git a/lib/gitlab/manifest_import/manifest.rb b/lib/gitlab/manifest_import/manifest.rb
new file mode 100644
index 00000000000..4d6034fb956
--- /dev/null
+++ b/lib/gitlab/manifest_import/manifest.rb
@@ -0,0 +1,81 @@
+# Class to parse manifest file and build a list of repositories for import
+#
+# <manifest>
+# <remote review="https://android-review.googlesource.com/" />
+# <project path="platform-common" name="platform" />
+# <project path="platform/art" name="platform/art" />
+# <project path="platform/device" name="platform/device" />
+# </manifest>
+#
+# 1. Project path must be uniq and can't be part of other project path.
+# For example, you can't have projects with 'foo' and 'foo/bar' paths.
+# 2. Remote must be present with review attribute so GitLab knows
+# where to fetch source code
+module Gitlab
+ module ManifestImport
+ class Manifest
+ attr_reader :parsed_xml, :errors
+
+ def initialize(file)
+ @parsed_xml = Nokogiri::XML(file) { |config| config.strict }
+ @errors = []
+ rescue Nokogiri::XML::SyntaxError
+ @errors = ['The uploaded file is not a valid XML file.']
+ end
+
+ def projects
+ raw_projects.each_with_index.map do |project, i|
+ {
+ id: i,
+ name: project['name'],
+ path: project['path'],
+ url: repository_url(project['name'])
+ }
+ end
+ end
+
+ def valid?
+ return false if @errors.any?
+
+ unless validate_remote
+ @errors << 'Make sure a <remote> tag is present and is valid.'
+ end
+
+ unless validate_projects
+ @errors << 'Make sure every <project> tag has name and path attributes.'
+ end
+
+ @errors.empty?
+ end
+
+ private
+
+ def validate_remote
+ remote.present? && URI.parse(remote).host
+ rescue URI::Error
+ false
+ end
+
+ def validate_projects
+ raw_projects.all? do |project|
+ project['name'] && project['path']
+ end
+ end
+
+ def repository_url(name)
+ URI.join(remote, name).to_s
+ end
+
+ def remote
+ return @remote if defined?(@remote)
+
+ remote_tag = parsed_xml.css('manifest > remote').first
+ @remote = remote_tag['review'] if remote_tag
+ end
+
+ def raw_projects
+ @raw_projects ||= parsed_xml.css('manifest > project')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/manifest_import/project_creator.rb b/lib/gitlab/manifest_import/project_creator.rb
new file mode 100644
index 00000000000..b5967c93735
--- /dev/null
+++ b/lib/gitlab/manifest_import/project_creator.rb
@@ -0,0 +1,41 @@
+module Gitlab
+ module ManifestImport
+ class ProjectCreator
+ attr_reader :repository, :destination, :current_user
+
+ def initialize(repository, destination, current_user)
+ @repository = repository
+ @destination = destination
+ @current_user = current_user
+ end
+
+ def execute
+ group_full_path, _, project_path = repository[:path].rpartition('/')
+ group_full_path = File.join(destination.full_path, group_full_path) if destination
+ group = create_group_with_parents(group_full_path)
+
+ params = {
+ import_url: repository[:url],
+ import_type: 'manifest',
+ namespace_id: group.id,
+ path: project_path,
+ name: project_path,
+ visibility_level: destination.visibility_level
+ }
+
+ Projects::CreateService.new(current_user, params).execute
+ end
+
+ private
+
+ def create_group_with_parents(full_path)
+ params = {
+ group_path: full_path,
+ visibility_level: destination.visibility_level
+ }
+
+ Groups::NestedCreateService.new(current_user, params).execute
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0c2541b17a7..ab488218288 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -77,6 +77,9 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
+msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -202,6 +205,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
msgid "<strong>Removes</strong> source branch"
msgstr ""
@@ -283,6 +289,12 @@ msgstr ""
msgid "Add todo"
msgstr ""
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
msgid "AdminArea|Stop all jobs"
msgstr ""
@@ -463,6 +475,9 @@ msgstr ""
msgid "An error occurred. Please try again."
msgstr ""
+msgid "Any"
+msgstr ""
+
msgid "Appearance"
msgstr ""
@@ -481,6 +496,9 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
msgid "Are you sure you want to remove this identity?"
msgstr ""
@@ -685,6 +703,9 @@ msgstr ""
msgid "Below are examples of regex for existing tools:"
msgstr ""
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
msgid "Boards"
msgstr ""
@@ -975,6 +996,9 @@ msgstr ""
msgid "Choose file..."
msgstr ""
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
msgid "Choose which repositories you want to import."
msgstr ""
@@ -1657,9 +1681,6 @@ msgstr ""
msgid "Copy commit SHA to clipboard"
msgstr ""
-msgid "Copy file name to clipboard"
-msgstr ""
-
msgid "Copy file path to clipboard"
msgstr ""
@@ -1699,6 +1720,9 @@ msgstr ""
msgid "Create file"
msgstr ""
+msgid "Create group"
+msgstr ""
+
msgid "Create group label"
msgstr ""
@@ -1747,6 +1771,9 @@ msgstr ""
msgid "Created by me"
msgstr ""
+msgid "Created on:"
+msgstr ""
+
msgid "Cron Timezone"
msgstr ""
@@ -1953,6 +1980,9 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description:"
+msgstr ""
+
msgid "Details"
msgstr ""
@@ -1980,6 +2010,12 @@ msgstr ""
msgid "Discard draft"
msgstr ""
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
msgid "Dismiss Cycle Analytics introduction box"
msgstr ""
@@ -2043,6 +2079,9 @@ msgstr ""
msgid "Edit files in the editor and commit changes here"
msgstr ""
+msgid "Edit group: %{group_name}"
+msgstr ""
+
msgid "Edit identity for %{user_name}"
msgstr ""
@@ -2250,6 +2289,12 @@ msgstr ""
msgid "Expand sidebar"
msgstr ""
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
msgid "Explore groups"
msgstr ""
@@ -2429,18 +2474,33 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Group"
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
+msgid "Group Git LFS status:"
+msgstr ""
+
msgid "Group ID"
msgstr ""
msgid "Group Runners"
msgstr ""
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
+msgid "Group: %{group_name}"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -2465,6 +2525,9 @@ msgstr ""
msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
msgstr ""
+msgid "Groups"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -2629,6 +2692,9 @@ msgstr ""
msgid "Import in progress"
msgstr ""
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
msgid "Import repositories from GitHub"
msgstr ""
@@ -2838,6 +2904,9 @@ msgstr ""
msgid "List"
msgstr ""
+msgid "List available repositories"
+msgstr ""
+
msgid "List your GitHub repositories"
msgstr ""
@@ -2865,6 +2934,9 @@ msgstr ""
msgid "Locked to current projects"
msgstr ""
+msgid "Manage access"
+msgstr ""
+
msgid "Manage all notifications"
msgstr ""
@@ -2877,6 +2949,12 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
msgid "Mar"
msgstr ""
@@ -3027,6 +3105,9 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "Most stars"
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -3042,6 +3123,9 @@ msgstr ""
msgid "Name your individual key via a title"
msgstr ""
+msgid "Name:"
+msgstr ""
+
msgid "Nav|Help"
msgstr ""
@@ -3057,6 +3141,9 @@ msgstr ""
msgid "New"
msgstr ""
+msgid "New Group"
+msgstr ""
+
msgid "New Identity"
msgstr ""
@@ -3155,6 +3242,9 @@ msgstr ""
msgid "No messages were logged"
msgstr ""
+msgid "No public groups"
+msgstr ""
+
msgid "No repository"
msgstr ""
@@ -3341,6 +3431,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path:"
+msgstr ""
+
msgid "Pause"
msgstr ""
@@ -3701,6 +3794,9 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -3827,6 +3923,9 @@ msgstr ""
msgid "Read more"
msgstr ""
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
msgid "Readme"
msgstr ""
@@ -3893,6 +3992,9 @@ msgstr ""
msgid "Repository Settings"
msgstr ""
+msgid "Repository URL"
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
@@ -4383,6 +4485,9 @@ msgstr ""
msgid "Storage"
msgstr ""
+msgid "Storage:"
+msgstr ""
+
msgid "Subgroups"
msgstr ""
@@ -4938,6 +5043,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Trending"
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -5078,6 +5186,12 @@ msgstr ""
msgid "Visibility and access controls"
msgstr ""
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
msgid "VisibilityLevel|Internal"
msgstr ""
@@ -5282,6 +5396,9 @@ msgstr ""
msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}"
msgstr ""
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
@@ -5425,6 +5542,9 @@ msgstr ""
msgid "for this project"
msgstr ""
+msgid "here"
+msgstr ""
+
msgid "importing"
msgstr ""
diff --git a/qa/README.md b/qa/README.md
index a4b4398645e..be4cf89ebbc 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -55,7 +55,7 @@ Since the arguments would be passed to `rspec`, you could use all `rspec`
options there. For example, passing `--backtrace` and also line number:
```
-bin/qa Test::Instance http://localhost qa/specs/features/login/standard_spec.rb:3 --backtrace
+bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace
```
### Overriding the authenticated user
diff --git a/qa/qa.rb b/qa/qa.rb
index be7bcf7a2ad..ef83722de90 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -1,5 +1,7 @@
$: << File.expand_path(File.dirname(__FILE__))
+Encoding.default_external = 'UTF-8'
+
module QA
##
# GitLab QA runtime classes, mostly singletons.
@@ -41,14 +43,17 @@ module QA
autoload :Project, 'qa/factory/resource/project'
autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github'
+ autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :Branch, 'qa/factory/resource/branch'
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
+ autoload :User, 'qa/factory/resource/user'
autoload :ProjectMilestone, 'qa/factory/resource/project_milestone'
autoload :Wiki, 'qa/factory/resource/wiki'
+ autoload :Fork, 'qa/factory/resource/fork'
end
module Repository
@@ -107,6 +112,7 @@ module QA
module Main
autoload :Login, 'qa/page/main/login'
autoload :OAuth, 'qa/page/main/oauth'
+ autoload :SignUp, 'qa/page/main/sign_up'
end
module Settings
@@ -167,6 +173,10 @@ module QA
autoload :Index, 'qa/page/project/issue/index'
end
+ module Fork
+ autoload :New, 'qa/page/project/fork/new'
+ end
+
module Milestone
autoload :New, 'qa/page/project/milestone/new'
autoload :Index, 'qa/page/project/milestone/index'
@@ -200,6 +210,10 @@ module QA
autoload :Sidebar, 'qa/page/issuable/sidebar'
end
+ module Layout
+ autoload :Banner, 'qa/page/layout/banner'
+ end
+
module MergeRequest
autoload :New, 'qa/page/merge_request/new'
autoload :Show, 'qa/page/merge_request/show'
diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb
index 48674c08a8d..4f78098d348 100644
--- a/qa/qa/factory/repository/project_push.rb
+++ b/qa/qa/factory/repository/project_push.rb
@@ -11,6 +11,8 @@ module QA
factory.output
end
+ product(:project) { |factory| factory.project }
+
def initialize
@file_name = 'file.txt'
@file_content = '# This is test project'
diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb
index 4f97e65b091..5b7ebf6c41f 100644
--- a/qa/qa/factory/repository/push.rb
+++ b/qa/qa/factory/repository/push.rb
@@ -5,7 +5,8 @@ module QA
module Repository
class Push < Factory::Base
attr_accessor :file_name, :file_content, :commit_message,
- :branch_name, :new_branch, :output, :repository_uri
+ :branch_name, :new_branch, :output, :repository_uri,
+ :user
attr_writer :remote_branch
@@ -31,9 +32,20 @@ module QA
def fabricate!
Git::Repository.perform do |repository|
repository.uri = repository_uri
+
repository.use_default_credentials
+ username = 'GitLab QA'
+ email = 'root@gitlab.com'
+
+ if user
+ repository.username = user.username
+ repository.password = user.password
+ username = user.name
+ email = user.email
+ end
+
repository.clone
- repository.configure_identity('GitLab QA', 'root@gitlab.com')
+ repository.configure_identity(username, email)
if new_branch
repository.checkout_new_branch(branch_name)
diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb
new file mode 100644
index 00000000000..1d0c76a3d30
--- /dev/null
+++ b/qa/qa/factory/resource/fork.rb
@@ -0,0 +1,24 @@
+module QA
+ module Factory
+ module Resource
+ class Fork < Factory::Base
+ dependency Factory::Repository::ProjectPush, as: :push
+
+ dependency Factory::Resource::User, as: :user
+
+ product(:user) { |factory| factory.user }
+
+ def fabricate!
+ push.project.visit!
+ Page::Project::Show.act { fork_project }
+
+ Page::Project::Fork::New.perform do |fork_new|
+ fork_new.choose_namespace(user.name)
+ end
+
+ Page::Layout::Banner.act { has_notice?('The project was successfully forked.') }
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb
index f32cf985e9d..1c9e5f94b22 100644
--- a/qa/qa/factory/resource/kubernetes_cluster.rb
+++ b/qa/qa/factory/resource/kubernetes_cluster.rb
@@ -36,6 +36,9 @@ module QA
if @install_helm_tiller
Page::Project::Operations::Kubernetes::Show.perform do |page|
+ # We must wait a few seconds for permissions to be setup correctly for new cluster
+ sleep 10
+
# Helm must be installed before everything else
page.install!(:helm)
page.await_installed(:helm)
diff --git a/qa/qa/factory/resource/merge_request_from_fork.rb b/qa/qa/factory/resource/merge_request_from_fork.rb
new file mode 100644
index 00000000000..6caaf65f673
--- /dev/null
+++ b/qa/qa/factory/resource/merge_request_from_fork.rb
@@ -0,0 +1,24 @@
+module QA
+ module Factory
+ module Resource
+ class MergeRequestFromFork < MergeRequest
+ attr_accessor :fork_branch
+
+ dependency Factory::Resource::Fork, as: :fork
+
+ dependency Factory::Repository::ProjectPush, as: :push do |push, factory|
+ push.project = factory.fork
+ push.branch_name = factory.fork_branch
+ push.file_name = 'file2.txt'
+ push.user = factory.fork.user
+ end
+
+ def fabricate!
+ fork.visit!
+ Page::Project::Show.act { new_merge_request }
+ Page::MergeRequest::New.act { create_merge_request }
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb
index 7bc64c6ae5d..7fff22b5468 100644
--- a/qa/qa/factory/resource/project.rb
+++ b/qa/qa/factory/resource/project.rb
@@ -37,6 +37,7 @@ module QA
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
+ page.set_visibility('Public')
page.create_new_project
end
end
diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb
new file mode 100644
index 00000000000..e08df9e0cd0
--- /dev/null
+++ b/qa/qa/factory/resource/user.rb
@@ -0,0 +1,34 @@
+require 'securerandom'
+
+module QA
+ module Factory
+ module Resource
+ class User < Factory::Base
+ attr_accessor :name, :username, :email, :password
+
+ def initialize
+ @name = "name-#{SecureRandom.hex(8)}"
+ @username = "username-#{SecureRandom.hex(8)}"
+ @email = "mail#{SecureRandom.hex(8)}@mail.com"
+ @password = 'password'
+ end
+
+ product(:name) { |factory| factory.name }
+
+ product(:username) { |factory| factory.username }
+
+ product(:email) { |factory| factory.email }
+
+ product(:password) { |factory| factory.password }
+
+ def fabricate!
+ Page::Menu::Main.act { sign_out }
+ Page::Main::Login.act { switch_to_register_tab }
+ Page::Main::SignUp.perform do |page|
+ page.sign_up!(name: name, username: username, email: email, password: password)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb
index dec2ce1eab3..f207264e24f 100644
--- a/qa/qa/page/issuable/sidebar.rb
+++ b/qa/qa/page/issuable/sidebar.rb
@@ -4,6 +4,7 @@ module QA
class Sidebar < Page::Base
view 'app/views/shared/issuable/_sidebar.html.haml' do
element :labels_block, ".issuable-show-labels"
+ element :milestones_block, '.block.milestone'
end
def has_label?(label)
@@ -11,6 +12,12 @@ module QA
!!find('span', text: label)
end
end
+
+ def has_milestone?(milestone)
+ page.within('.block.milestone') do
+ !!find("[href*='/milestones/']", text: milestone)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/layout/banner.rb b/qa/qa/page/layout/banner.rb
new file mode 100644
index 00000000000..e7654bdafc9
--- /dev/null
+++ b/qa/qa/page/layout/banner.rb
@@ -0,0 +1,17 @@
+module QA
+ module Page
+ module Layout
+ class Banner < Page::Base
+ view 'app/views/layouts/header/_read_only_banner.html.haml' do
+ element :flash_notice, ".flash-notice"
+ end
+
+ def has_notice?(message)
+ page.within('.flash-notice') do
+ !!find('span', text: message)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 26c99efc53d..6cdfbd1c125 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -25,19 +25,24 @@ module QA
element :standard_tab, "link_to 'Standard'"
end
+ view 'app/views/devise/shared/_tabs_normal.html.haml' do
+ element :sign_in_tab, /nav-link.*login-pane.*Sign in/
+ element :register_tab, /nav-link.*register-pane.*Register/
+ end
+
def initialize
# The login page is usually the entry point for all the scenarios so
# we need to wait for the instance to start. That said, in some cases
# we are already logged-in so we check both cases here.
wait(max: 500) do
page.has_css?('.login-page') ||
- Page::Menu::Main.act { has_personal_area? }
+ Page::Menu::Main.act { has_personal_area?(wait: 0) }
end
end
def sign_in_using_credentials
# Don't try to log-in if we're already logged-in
- return if Page::Menu::Main.act { has_personal_area? }
+ return if Page::Menu::Main.act { has_personal_area?(wait: 0) }
using_wait_time 0 do
set_initial_password_if_present
@@ -48,12 +53,22 @@ module QA
sign_in_using_gitlab_credentials
end
end
+
+ Page::Menu::Main.act { has_personal_area? }
end
def self.path
'/users/sign_in'
end
+ def switch_to_sign_in_tab
+ click_on 'Sign in'
+ end
+
+ def switch_to_register_tab
+ click_on 'Register'
+ end
+
private
def sign_in_using_ldap_credentials
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
new file mode 100644
index 00000000000..9a834e94b81
--- /dev/null
+++ b/qa/qa/page/main/sign_up.rb
@@ -0,0 +1,27 @@
+module QA
+ module Page
+ module Main
+ class SignUp < Page::Base
+ view 'app/views/devise/shared/_signup_box.html.haml' do
+ element :name, 'text_field :name'
+ element :username, 'text_field :username'
+ element :email_field, 'email_field :email'
+ element :email_confirmation, 'email_field :email_confirmation'
+ element :password, 'password_field :password'
+ element :register_button, 'submit "Register"'
+ end
+
+ def sign_up!(name:, username:, email:, password:)
+ fill_in :new_user_name, with: name
+ fill_in :new_user_username, with: username
+ fill_in :new_user_email, with: email
+ fill_in :new_user_email_confirmation, with: email
+ fill_in :new_user_password, with: password
+ click_button 'Register'
+
+ Page::Menu::Main.act { has_personal_area? }
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb
index aef5c9f9c82..36e7285f7b7 100644
--- a/qa/qa/page/menu/main.rb
+++ b/qa/qa/page/menu/main.rb
@@ -60,9 +60,9 @@ module QA
end
end
- def has_personal_area?
+ def has_personal_area?(wait: Capybara.default_max_wait_time)
# No need to wait, either we're logged-in, or not.
- using_wait_time(0) { page.has_selector?('.qa-user-avatar') }
+ using_wait_time(wait) { page.has_selector?('.qa-user-avatar') }
end
private
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index f3200160a78..c200f14f4fb 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -79,12 +79,6 @@ module QA
click_element :squash_checkbox
end
-
- def has_milestone?(milestone_title)
- page.within('.issuable-sidebar') do
- !!find("[href*='/milestones/']", text: milestone_title, wait: 1)
- end
- end
end
end
end
diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb
new file mode 100644
index 00000000000..ed92df956bf
--- /dev/null
+++ b/qa/qa/page/project/fork/new.rb
@@ -0,0 +1,17 @@
+module QA
+ module Page
+ module Project
+ module Fork
+ class New < Page::Base
+ view 'app/views/projects/forks/_fork_button.html.haml' do
+ element :namespace, 'link_to project_forks_path'
+ end
+
+ def choose_namespace(namespace = Runtime::Namespace.path)
+ click_on namespace
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 7976e96d43b..9e812fa7c74 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -14,6 +14,7 @@ module QA
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
+ element :visibility_radios, 'visibility_level:'
end
view 'app/views/projects/_import_project_pane.html.haml' do
@@ -42,6 +43,10 @@ module QA
click_on 'Create project'
end
+ def set_visibility(visibility)
+ choose visibility
+ end
+
def go_to_github_import
click_link 'GitHub'
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 1dcdb59490a..88861d5772d 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -22,6 +22,11 @@ module QA
element :branches_dropdown
end
+ view 'app/views/projects/buttons/_fork.html.haml' do
+ element :fork_label, "%span= s_('GoToYourFork|Fork')"
+ element :fork_link, "link_to new_project_fork_path(@project)"
+ end
+
view 'app/views/projects/_files.html.haml' do
element :tree_holder, '.tree-holder'
end
@@ -61,6 +66,10 @@ module QA
click_link 'New issue'
end
+
+ def fork_project
+ click_on 'Fork'
+ end
end
end
end
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index 28f17d1160b..f1c8ef11f94 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -8,7 +8,7 @@ module QA
end
def name
- "qa-test-#{time.strftime('%Y-%m-%d-%Y-%H-%M-%S')}"
+ "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}"
end
def path
diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb
deleted file mode 100644
index 254f47cf217..00000000000
--- a/qa/qa/specs/features/login/standard_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module QA
- describe 'standard user login', :core do
- it 'user logs in using credentials' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- # TODO, since `Signed in successfully` message was removed
- # this is the only way to tell if user is signed in correctly.
- #
- Page::Menu::Main.perform do |menu|
- expect(menu).to have_personal_area
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb
index 5807e539699..36d7efb02e1 100644
--- a/qa/qa/specs/features/merge_request/create_spec.rb
+++ b/qa/qa/specs/features/merge_request/create_spec.rb
@@ -20,11 +20,12 @@ module QA
merge_request.milestone = current_milestone
end
- Page::MergeRequest::Show.perform do |merge_request|
- expect(page).to have_content('This is a merge request with a milestone')
- expect(page).to have_content('Great feature with milestone')
- expect(page).to have_content(/Opened [\w\s]+ ago/)
- expect(merge_request).to have_milestone(current_milestone.title)
+ expect(page).to have_content('This is a merge request with a milestone')
+ expect(page).to have_content('Great feature with milestone')
+ expect(page).to have_content(/Opened [\w\s]+ ago/)
+
+ Page::Issuable::Sidebar.perform do |sidebar|
+ expect(sidebar).to have_milestone(current_milestone.title)
end
end
end
diff --git a/qa/qa/specs/features/project/fork_project_spec.rb b/qa/qa/specs/features/project/fork_project_spec.rb
new file mode 100644
index 00000000000..8ad0120305a
--- /dev/null
+++ b/qa/qa/specs/features/project/fork_project_spec.rb
@@ -0,0 +1,23 @@
+module QA
+ describe 'Project fork', :core do
+ it 'can submit merge requests to upstream master' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request|
+ merge_request.fork_branch = 'feature-branch'
+ end
+
+ Page::Menu::Main.act { sign_out }
+ Page::Main::Login.act do
+ switch_to_sign_in_tab
+ sign_in_using_credentials
+ end
+
+ merge_request.visit!
+ Page::MergeRequest::Show.act { merge! }
+
+ expect(page).to have_content('The changes were merged')
+ end
+ end
+end
diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb
index 29ea2e69ec7..4e593a69aae 100644
--- a/qa/qa/specs/features/repository/protected_branches_spec.rb
+++ b/qa/qa/specs/features/repository/protected_branches_spec.rb
@@ -13,15 +13,11 @@ module QA
Page::Main::Login.act { sign_in_using_credentials }
end
- after do |example|
+ after do
# We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()'
-
- # In order to help diagnose a false failure
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/48241
- log_push_output if example.exception
end
context 'when developers and maintainers are allowed to push to a protected branch' do
@@ -31,9 +27,9 @@ module QA
expect(protected_branch.name).to have_content(branch_name)
expect(protected_branch.push_allowance).to have_content('Developers + Maintainers')
- @push = push_new_file(branch_name)
+ push = push_new_file(branch_name)
- expect(@push.output).to match(/remote: To create a merge request for protected-branch, visit/)
+ expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/)
end
end
@@ -41,11 +37,11 @@ module QA
it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
- @push = push_new_file(branch_name)
+ push = push_new_file(branch_name)
- expect(@push.output)
+ expect(push.output)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
- expect(@push.output)
+ expect(push.output)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
@@ -69,13 +65,5 @@ module QA
resource.new_branch = false
end
end
-
- def log_push_output
- if defined?(@push)
- filename = File.join('tmp', "push-output-#{project.name}")
- puts "Exception detected. Push output will be saved to #{filename}"
- IO.binwrite(filename, @push.output)
- end
- end
end
end
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index 7376841fac8..c7c83369d7c 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -15,55 +15,16 @@ describe MetricsController do
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir)
allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true)
allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range])
+ allow_any_instance_of(MetricsService).to receive(:metrics_text).and_return("prometheus_counter 1")
end
describe '#index' do
shared_examples_for 'endpoint providing metrics' do
- it 'returns DB ping metrics' do
+ it 'returns prometheus metrics' do
get :index
- expect(response.body).to match(/^db_ping_timeout 0$/)
- expect(response.body).to match(/^db_ping_success 1$/)
- expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/)
- end
-
- it 'returns Redis ping metrics' do
- get :index
-
- expect(response.body).to match(/^redis_ping_timeout 0$/)
- expect(response.body).to match(/^redis_ping_success 1$/)
- expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/)
- end
-
- it 'returns Caching ping metrics' do
- get :index
-
- expect(response.body).to match(/^redis_cache_ping_timeout 0$/)
- expect(response.body).to match(/^redis_cache_ping_success 1$/)
- expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/)
- end
-
- it 'returns Queues ping metrics' do
- get :index
-
- expect(response.body).to match(/^redis_queues_ping_timeout 0$/)
- expect(response.body).to match(/^redis_queues_ping_success 1$/)
- expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/)
- end
-
- it 'returns SharedState ping metrics' do
- get :index
-
- expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/)
- expect(response.body).to match(/^redis_shared_state_ping_success 1$/)
- expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/)
- end
-
- it 'returns Gitaly metrics' do
- get :index
-
- expect(response.body).to match(/^gitaly_health_check_success{shard="default"} 1$/)
- expect(response.body).to match(/^gitaly_health_check_latency_seconds{shard="default"} [0-9\.]+$/)
+ expect(response.status).to eq(200)
+ expect(response.body).to match(/^prometheus_counter 1$/)
end
context 'prometheus metrics are disabled' do
@@ -101,7 +62,7 @@ describe MetricsController do
allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip)
end
- it 'returns proper response' do
+ it 'returns the expected error response' do
get :index
expect(response.status).to eq(404)
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
new file mode 100644
index 00000000000..e381d073804
--- /dev/null
+++ b/spec/features/import/manifest_import_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do
+ include Select2Helper
+
+ let(:user) { create(:admin) }
+ let(:group) { create(:group) }
+
+ before do
+ sign_in(user)
+
+ group.add_owner(user)
+ end
+
+ it 'parses manifest file and list repositories' do
+ visit new_import_manifest_path
+
+ attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
+ click_on 'List available repositories'
+
+ expect(page).to have_button('Import all repositories')
+ expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint')
+ end
+
+ it 'imports succesfully imports a project' do
+ visit new_import_manifest_path
+
+ attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
+ click_on 'List available repositories'
+
+ page.within(first_row) do
+ click_on 'Import'
+
+ expect(page).to have_content 'Done'
+ expect(page).to have_content("#{group.full_path}/build/make")
+ end
+ end
+
+ it 'renders an error if invalid file was provided' do
+ visit new_import_manifest_path
+
+ attach_file('manifest', Rails.root.join('spec/fixtures/banana_sample.gif'))
+ click_on 'List available repositories'
+
+ expect(page).to have_content 'The uploaded file is not a valid XML file.'
+ end
+
+ def first_row
+ page.all('table.import-jobs tbody tr')[0]
+ end
+end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index df8528e79dd..bbe08ff83ff 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -25,6 +25,22 @@ describe 'New project' do
expect(page).to have_link('GitLab export')
end
+ describe 'manifest import option' do
+ before do
+ visit new_project_path
+
+ find('#import-project-tab').click
+ end
+
+ context 'when using postgres', :postgresql do
+ it { expect(page).to have_link('Manifest file') }
+ end
+
+ context 'when using mysql', :mysql do
+ it { expect(page).not_to have_link('Manifest file') }
+ end
+ end
+
context 'Visibility level selector', :js do
Gitlab::VisibilityLevel.options.each do |key, level|
it "sets selector to #{key}" do
@@ -201,5 +217,16 @@ describe 'New project' do
expect(current_path).to eq new_import_google_code_path
end
end
+
+ context 'from manifest file', :postgresql do
+ before do
+ first('.import_manifest').click
+ end
+
+ it 'shows import instructions' do
+ expect(page).to have_content('Manifest file import')
+ expect(current_path).to eq new_import_manifest_path
+ end
+ end
end
end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index 31b105229be..546619e88ec 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -16,4 +16,36 @@ describe 'Projects > Show > User manages notifications', :js do
expect(page).to have_content 'On mention'
end
end
+
+ context 'custom notification settings' do
+ let(:email_events) do
+ [
+ :new_note,
+ :new_issue,
+ :reopen_issue,
+ :close_issue,
+ :reassign_issue,
+ :issue_due,
+ :new_merge_request,
+ :push_to_merge_request,
+ :reopen_merge_request,
+ :close_merge_request,
+ :reassign_merge_request,
+ :merge_merge_request,
+ :failed_pipeline,
+ :success_pipeline
+ ]
+ end
+
+ it 'shows notification settings checkbox' do
+ first('.notifications-btn').click
+ page.find('a[data-notification-level="custom"]').click
+
+ page.within('.custom-notifications-form') do
+ email_events.each do |event_name|
+ expect(page).to have_selector("input[name='notification_setting[#{event_name}]']")
+ end
+ end
+ end
+ end
end
diff --git a/spec/fixtures/aosp_manifest.xml b/spec/fixtures/aosp_manifest.xml
new file mode 100644
index 00000000000..cfd0094b735
--- /dev/null
+++ b/spec/fixtures/aosp_manifest.xml
@@ -0,0 +1,685 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest>
+
+ <remote name="aosp"
+ fetch=".."
+ review="https://android-review.googlesource.com/" />
+ <default revision="master"
+ remote="aosp"
+ sync-j="4" />
+
+ <project path="build/make" name="platform/build" groups="pdk" >
+ <copyfile src="core/root.mk" dest="Makefile" />
+ <linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" />
+ <linkfile src="buildspec.mk.default" dest="build/buildspec.mk.default" />
+ <linkfile src="core" dest="build/core" />
+ <linkfile src="envsetup.sh" dest="build/envsetup.sh" />
+ <linkfile src="target" dest="build/target" />
+ <linkfile src="tools" dest="build/tools" />
+ </project>
+ <project path="build/blueprint" name="platform/build/blueprint" groups="pdk,tradefed" />
+ <project path="build/kati" name="platform/build/kati" groups="pdk,tradefed" />
+ <project path="build/soong" name="platform/build/soong" groups="pdk,tradefed" >
+ <linkfile src="root.bp" dest="Android.bp" />
+ <linkfile src="bootstrap.bash" dest="bootstrap.bash" />
+ </project>
+ <project path="art" name="platform/art" groups="pdk" />
+ <project path="bionic" name="platform/bionic" groups="pdk" />
+ <project path="bootable/recovery" name="platform/bootable/recovery" groups="pdk" />
+ <project path="compatibility/cdd" name="platform/compatibility/cdd" groups="pdk" />
+ <project path="cts" name="platform/cts" groups="cts,pdk-cw-fs,pdk-fs" />
+ <project path="dalvik" name="platform/dalvik" groups="pdk-cw-fs,pdk-fs" />
+ <project path="developers/build" name="platform/developers/build" groups="developers" />
+ <project path="developers/demos" name="platform/developers/demos" groups="developers" />
+ <project path="developers/samples/android" name="platform/developers/samples/android" groups="developers" />
+ <project path="development" name="platform/development" groups="developers,pdk-cw-fs,pdk-fs" />
+ <project path="device/asus/fugu" name="device/asus/fugu" groups="device,fugu,broadcom_pdk" />
+ <project path="device/asus/fugu-kernel" name="device/asus/fugu-kernel" groups="device,fugu,broadcom_pdk" clone-depth="1" />
+ <project path="device/common" name="device/common" groups="pdk-cw-fs,pdk" />
+ <project path="device/generic/arm64" name="device/generic/arm64" groups="pdk" />
+ <project path="device/generic/armv7-a-neon" name="device/generic/armv7-a-neon" groups="pdk" />
+ <project path="device/generic/car" name="device/generic/car" groups="pdk" />
+ <project path="device/generic/common" name="device/generic/common" groups="pdk" />
+ <project path="device/generic/goldfish" name="device/generic/goldfish" groups="pdk" />
+ <project path="device/generic/goldfish-opengl" name="device/generic/goldfish-opengl" groups="pdk" />
+ <project path="device/generic/mini-emulator-arm64" name="device/generic/mini-emulator-arm64" groups="pdk" />
+ <project path="device/generic/mini-emulator-armv7-a-neon" name="device/generic/mini-emulator-armv7-a-neon" groups="pdk" />
+ <project path="device/generic/mini-emulator-x86" name="device/generic/mini-emulator-x86" groups="pdk" />
+ <project path="device/generic/mini-emulator-x86_64" name="device/generic/mini-emulator-x86_64" groups="pdk" />
+ <project path="device/generic/qemu" name="device/generic/qemu" groups="pdk" />
+ <project path="device/generic/uml" name="device/generic/uml" groups="device,pdk" />
+ <project path="device/generic/x86" name="device/generic/x86" groups="pdk" />
+ <project path="device/generic/x86_64" name="device/generic/x86_64" groups="pdk" />
+ <project path="device/google/accessory/arduino" name="device/google/accessory/arduino" groups="device,pdk" />
+ <project path="device/google/accessory/demokit" name="device/google/accessory/demokit" groups="device,pdk" />
+ <project path="device/google/atv" name="device/google/atv" groups="device,broadcom_pdk,generic_fs,pdk" />
+ <project path="device/google/contexthub" name="device/google/contexthub" groups="device,marlin,pdk" />
+ <project path="device/google/cuttlefish" name="device/google/cuttlefish" groups="device" />
+ <project path="device/google/cuttlefish_common" name="device/google/cuttlefish_common" groups="device" />
+ <project path="device/google/cuttlefish_kernel" name="device/google/cuttlefish_kernel" groups="device" clone-depth="1" />
+ <project path="device/google/dragon" name="device/google/dragon" groups="device,dragon" />
+ <project path="device/google/dragon-kernel" name="device/google/dragon-kernel" groups="device,dragon" clone-depth="1" />
+ <project path="device/google/marlin" name="device/google/marlin" groups="device,marlin,pdk" />
+ <project path="device/google/marlin-kernel" name="device/google/marlin-kernel" groups="device,marlin,pdk" clone-depth="1" />
+ <project path="device/google/muskie" name="device/google/muskie" groups="device,muskie" />
+ <project path="device/google/taimen" name="device/google/taimen" groups="device,taimen" />
+ <project path="device/google/vrservices" name="device/google/vrservices" groups="pdk" clone-depth="1" />
+ <project path="device/google/wahoo" name="device/google/wahoo" groups="device,wahoo" />
+ <project path="device/google/wahoo-kernel" name="device/google/wahoo-kernel" groups="device,wahoo" clone-depth="1" />
+ <project path="device/huawei/angler" name="device/huawei/angler" groups="device,angler,broadcom_pdk" />
+ <project path="device/huawei/angler-kernel" name="device/huawei/angler-kernel" groups="device,angler,broadcom_pdk" clone-depth="1" />
+ <project path="device/lge/bullhead" name="device/lge/bullhead" groups="device,bullhead" />
+ <project path="device/lge/bullhead-kernel" name="device/lge/bullhead-kernel" groups="device,bullhead" clone-depth="1" />
+ <project path="device/linaro/bootloader/arm-trusted-firmware" name="device/linaro/bootloader/arm-trusted-firmware" />
+ <project path="device/linaro/bootloader/edk2" name="device/linaro/bootloader/edk2" />
+ <project path="device/linaro/bootloader/OpenPlatformPkg" name="device/linaro/bootloader/OpenPlatformPkg" />
+ <project path="device/linaro/hikey" name="device/linaro/hikey" groups="device,hikey,pdk" />
+ <project path="device/linaro/hikey-kernel" name="device/linaro/hikey-kernel" groups="device,hikey,pdk" clone-depth="1" />
+ <project path="device/sample" name="device/sample" groups="pdk" />
+ <project path="external/aac" name="platform/external/aac" groups="pdk" />
+ <project path="external/abi-compliance-checker" name="platform/external/abi-compliance-checker" groups="pdk" />
+ <project path="external/abi-dumper" name="platform/external/abi-dumper" groups="pdk" />
+ <project path="external/adt-infra" name="platform/external/adt-infra" groups="adt-infra,notdefault,pdk-fs" />
+ <project path="external/android-clat" name="platform/external/android-clat" groups="pdk" />
+ <project path="external/androidplot" name="platform/external/androidplot" groups="pdk" />
+ <project path="external/annotation-tools" name="platform/external/annotation-tools" groups="pdk" />
+ <project path="external/ant-glob" name="platform/external/ant-glob" groups="pdk" />
+ <project path="external/antlr" name="platform/external/antlr" groups="pdk" />
+ <project path="external/apache-commons-math" name="platform/external/apache-commons-math" groups="pdk" />
+ <project path="external/apache-harmony" name="platform/external/apache-harmony" groups="pdk" />
+ <project path="external/apache-http" name="platform/external/apache-http" groups="pdk" />
+ <project path="external/apache-xml" name="platform/external/apache-xml" groups="pdk" />
+ <project path="external/archive-patcher" name="platform/external/archive-patcher" groups="pdk" />
+ <project path="external/arm-neon-tests" name="platform/external/arm-neon-tests" groups="vendor" />
+ <project path="external/autotest" name="platform/external/autotest" groups="pdk-fs" />
+ <project path="external/avb" name="platform/external/avb" groups="pdk" />
+ <project path="external/bart" name="platform/external/bart" groups="pdk" />
+ <project path="external/blktrace" name="platform/external/blktrace" groups="pdk" />
+ <project path="external/boringssl" name="platform/external/boringssl" groups="pdk" />
+ <project path="external/bouncycastle" name="platform/external/bouncycastle" groups="pdk" />
+ <project path="external/brotli" name="platform/external/brotli" groups="pdk" />
+ <project path="external/bsdiff" name="platform/external/bsdiff" groups="pdk" />
+ <project path="external/bzip2" name="platform/external/bzip2" groups="pdk" />
+ <project path="external/caliper" name="platform/external/caliper" groups="pdk" />
+ <project path="external/cblas" name="platform/external/cblas" groups="pdk" />
+ <project path="external/chromium-libpac" name="platform/external/chromium-libpac" groups="pdk" />
+ <project path="external/chromium-trace" name="platform/external/chromium-trace" groups="pdk" />
+ <project path="external/chromium-webview" name="platform/external/chromium-webview" groups="pdk" clone-depth="1" />
+ <project path="external/clang" name="platform/external/clang" groups="pdk" />
+ <project path="external/cldr" name="platform/external/cldr" groups="pdk" />
+ <project path="external/cmockery" name="platform/external/cmockery" groups="pdk" />
+ <project path="external/cn-cbor" name="platform/external/cn-cbor" groups="pdk" />
+ <project path="external/compiler-rt" name="platform/external/compiler-rt" groups="pdk" />
+ <project path="external/conscrypt" name="platform/external/conscrypt" groups="pdk" />
+ <project path="external/crcalc" name="platform/external/crcalc" groups="pdk" />
+ <project path="external/cros/system_api" name="platform/external/cros/system_api" groups="pdk" />
+ <project path="external/curl" name="platform/external/curl" groups="pdk" />
+ <project path="external/dagger2" name="platform/external/dagger2" groups="pdk" />
+ <project path="external/deqp" name="platform/external/deqp" groups="pdk-fs" />
+ <project path="external/desugar" name="platform/external/desugar" groups="pdk" />
+ <project path="external/devlib" name="platform/external/devlib" groups="pdk" />
+ <project path="external/dexmaker" name="platform/external/dexmaker" groups="pdk" />
+ <project path="external/dhcpcd-6.8.2" name="platform/external/dhcpcd-6.8.2" groups="pdk" />
+ <project path="external/dlmalloc" name="platform/external/dlmalloc" groups="pdk" />
+ <project path="external/dng_sdk" name="platform/external/dng_sdk" groups="pdk" />
+ <project path="external/dnsmasq" name="platform/external/dnsmasq" groups="pdk" />
+ <project path="external/doclava" name="platform/external/doclava" groups="pdk" />
+ <project path="external/dokka" name="platform/external/dokka" groups="pdk" />
+ <project path="external/drm_hwcomposer" name="platform/external/drm_hwcomposer" groups="drm_hwcomposer,pdk-fs" />
+ <project path="external/droiddriver" name="platform/external/droiddriver" groups="pdk" />
+ <project path="external/drrickorang" name="platform/external/drrickorang" groups="pdk" />
+ <project path="external/dtc" name="platform/external/dtc" groups="pdk"/>
+ <project path="external/e2fsprogs" name="platform/external/e2fsprogs" groups="pdk" />
+ <project path="external/easymock" name="platform/external/easymock" groups="pdk" />
+ <project path="external/eigen" name="platform/external/eigen" groups="pdk" />
+ <project path="external/elfutils" name="platform/external/elfutils" groups="pdk" />
+ <project path="external/emma" name="platform/external/emma" groups="pdk" />
+ <project path="external/error_prone" name="platform/external/error_prone" groups="pdk" />
+ <project path="external/esd" name="platform/external/esd" groups="pdk" />
+ <project path="external/expat" name="platform/external/expat" groups="pdk" />
+ <project path="external/eyes-free" name="platform/external/eyes-free" groups="pdk" />
+ <project path="external/f2fs-tools" name="platform/external/f2fs-tools" groups="pdk" />
+ <project path="external/fdlibm" name="platform/external/fdlibm" groups="pdk" />
+ <project path="external/fec" name="platform/external/fec" groups="pdk" />
+ <project path="external/flac" name="platform/external/flac" groups="pdk" />
+ <project path="external/flatbuffers" name="platform/external/flatbuffers" groups="pdk" />
+ <project path="external/fonttools" name="platform/external/fonttools" groups="pdk" />
+ <project path="external/freetype" name="platform/external/freetype" groups="pdk" />
+ <project path="external/fsck_msdos" name="platform/external/fsck_msdos" groups="pdk" />
+ <project path="external/gemmlowp" name="platform/external/gemmlowp" groups="pdk" />
+ <project path="external/gflags" name="platform/external/gflags" groups="pdk" />
+ <project path="external/giflib" name="platform/external/giflib" groups="pdk,qcom_msm8x26" />
+ <project path="external/glide" name="platform/external/glide" groups="pdk" />
+ <project path="external/golang-protobuf" name="platform/external/golang-protobuf" groups="pdk" />
+ <project path="external/google-benchmark" name="platform/external/google-benchmark" groups="pdk" />
+ <project path="external/google-breakpad" name="platform/external/google-breakpad" groups="pdk-fs" />
+ <project path="external/google-fonts/carrois-gothic-sc" name="platform/external/google-fonts/carrois-gothic-sc" groups="pdk" />
+ <project path="external/google-fonts/coming-soon" name="platform/external/google-fonts/coming-soon" groups="pdk" />
+ <project path="external/google-fonts/cutive-mono" name="platform/external/google-fonts/cutive-mono" groups="pdk" />
+ <project path="external/google-fonts/dancing-script" name="platform/external/google-fonts/dancing-script" groups="pdk" />
+ <project path="external/google-styleguide" name="platform/external/google-styleguide" groups="pdk" />
+ <project path="external/google-tv-pairing-protocol" name="platform/external/google-tv-pairing-protocol" groups="pdk" />
+ <project path="external/googletest" name="platform/external/googletest" groups="pdk" />
+ <project path="external/gptfdisk" name="platform/external/gptfdisk" groups="pdk" />
+ <project path="external/guava" name="platform/external/guava" groups="pdk" />
+ <project path="external/guice" name="platform/external/guice" groups="pdk" />
+ <project path="external/hamcrest" name="platform/external/hamcrest" groups="pdk" />
+ <project path="external/harfbuzz_ng" name="platform/external/harfbuzz_ng" groups="pdk,qcom_msm8x26" />
+ <project path="external/hyphenation-patterns" name="platform/external/hyphenation-patterns" groups="pdk" />
+ <project path="external/icu" name="platform/external/icu" groups="pdk" />
+ <project path="external/ImageMagick" name="platform/external/ImageMagick" groups="pdk" />
+ <project path="external/ims" name="platform/external/ims" groups="pdk" />
+ <project path="external/iproute2" name="platform/external/iproute2" groups="pdk" />
+ <project path="external/ipsec-tools" name="platform/external/ipsec-tools" groups="pdk" />
+ <project path="external/iptables" name="platform/external/iptables" groups="pdk" />
+ <project path="external/iputils" name="platform/external/iputils" groups="pdk" />
+ <project path="external/iw" name="platform/external/iw" groups="pdk" />
+ <project path="external/jacoco" name="platform/external/jacoco" groups="pdk" />
+ <project path="external/jarjar" name="platform/external/jarjar" groups="pdk" />
+ <project path="external/javaparser" name="platform/external/javaparser" groups="pdk" />
+ <project path="external/javasqlite" name="platform/external/javasqlite" groups="pdk" />
+ <project path="external/javassist" name="platform/external/javassist" groups="pdk" />
+ <project path="external/jcommander" name="platform/external/jcommander" groups="pdk" />
+ <project path="external/jdiff" name="platform/external/jdiff" groups="pdk" />
+ <project path="external/jemalloc" name="platform/external/jemalloc" groups="pdk" />
+ <project path="external/jline" name="platform/external/jline" groups="pdk,tradefed,pdk-fs" />
+ <project path="external/jmdns" name="platform/external/jmdns" groups="pdk" />
+ <project path="external/jsilver" name="platform/external/jsilver" groups="pdk" />
+ <project path="external/jsmn" name="platform/external/jsmn" groups="pdk" />
+ <project path="external/jsoncpp" name="platform/external/jsoncpp" groups="pdk" />
+ <project path="external/jsr305" name="platform/external/jsr305" groups="pdk" />
+ <project path="external/jsr330" name="platform/external/jsr330" groups="pdk" />
+ <project path="external/junit" name="platform/external/junit" groups="pdk" />
+ <project path="external/junit-params" name="platform/external/junit-params" groups="pdk" />
+ <project path="external/kernel-headers" name="platform/external/kernel-headers" groups="pdk" />
+ <project path="external/kmod" name="platform/external/kmod" groups="pdk" />
+ <project path="external/kotlinc" name="platform/external/kotlinc" groups="pdk" />
+ <project path="external/ksoap2" name="platform/external/ksoap2" groups="pdk" />
+ <project path="external/libavc" name="platform/external/libavc" groups="pdk" />
+ <project path="external/libbackup" name="platform/external/libbackup" groups="pdk" />
+ <project path="external/libbrillo" name="platform/external/libbrillo" groups="pdk" />
+ <project path="external/libcap" name="platform/external/libcap" groups="pdk" />
+ <project path="external/libcap-ng" name="platform/external/libcap-ng" groups="pdk" />
+ <project path="external/libchrome" name="platform/external/libchrome" groups="pdk" />
+ <project path="external/libconstrainedcrypto" name="platform/external/libconstrainedcrypto" groups="pdk" />
+ <project path="external/libcups" name="platform/external/libcups" groups="pdk-cw-fs,pdk-fs" />
+ <project path="external/libcxx" name="platform/external/libcxx" groups="pdk" />
+ <project path="external/libcxxabi" name="platform/external/libcxxabi" groups="pdk" />
+ <project path="external/libdaemon" name="platform/external/libdaemon" groups="pdk" />
+ <project path="external/libdivsufsort" name="platform/external/libdivsufsort" groups="pdk" />
+ <project path="external/libdrm" name="platform/external/libdrm" groups="pdk" />
+ <project path="external/libedit" name="platform/external/libedit" groups="pdk" />
+ <project path="external/libese" name="platform/external/libese" groups="pdk" />
+ <project path="external/libevent" name="platform/external/libevent" groups="pdk" />
+ <project path="external/libexif" name="platform/external/libexif" groups="pdk" />
+ <project path="external/libgsm" name="platform/external/libgsm" groups="pdk" />
+ <project path="external/libhevc" name="platform/external/libhevc" groups="pdk" />
+ <project path="external/libjpeg-turbo" name="platform/external/libjpeg-turbo" groups="pdk" />
+ <project path="external/libldac" name="platform/external/libldac" groups="pdk" />
+ <project path="external/libmicrohttpd" name="platform/external/libmicrohttpd" groups="pdk" />
+ <project path="external/libmpeg2" name="platform/external/libmpeg2" groups="pdk" />
+ <project path="external/libmtp" name="platform/external/libmtp" groups="pdk" />
+ <project path="external/libnetfilter_conntrack" name="platform/external/libnetfilter_conntrack" groups="pdk" />
+ <project path="external/libnfnetlink" name="platform/external/libnfnetlink" groups="pdk" />
+ <project path="external/libnl" name="platform/external/libnl" groups="pdk" />
+ <project path="external/libogg" name="platform/external/libogg" groups="pdk" />
+ <project path="external/libopus" name="platform/external/libopus" groups="pdk" />
+ <project path="external/libpcap" name="platform/external/libpcap" groups="pdk" />
+ <project path="external/libphonenumber" name="platform/external/libphonenumber" groups="pdk" />
+ <project path="external/libpng" name="platform/external/libpng" groups="pdk" />
+ <project path="external/libtextclassifier" name="platform/external/libtextclassifier" groups="pdk" />
+ <project path="external/libunwind" name="platform/external/libunwind" groups="pdk" />
+ <project path="external/libunwind_llvm" name="platform/external/libunwind_llvm" groups="pdk" />
+ <project path="external/libusb" name="platform/external/libusb" groups="pdk" />
+ <project path="external/libusb-compat" name="platform/external/libusb-compat" groups="pdk" />
+ <project path="external/libvncserver" name="platform/external/libvncserver" groups="pdk" />
+ <project path="external/libvorbis" name="platform/external/libvorbis" groups="pdk" />
+ <project path="external/libvpx" name="platform/external/libvpx" groups="pdk" />
+ <project path="external/libvterm" name="platform/external/libvterm" groups="pdk" />
+ <project path="external/libxcam" name="platform/external/libxcam" groups="pdk" />
+ <project path="external/libxml2" name="platform/external/libxml2" groups="pdk,libxml2" />
+ <project path="external/libyuv" name="platform/external/libyuv" groups="pdk,libyuv" />
+ <project path="external/linux-kselftest" name="platform/external/linux-kselftest" groups="vts,pdk" />
+ <project path="external/lisa" name="platform/external/lisa" groups="pdk" />
+ <project path="external/llvm" name="platform/external/llvm" groups="pdk" />
+ <project path="external/lmfit" name="platform/external/lmfit" groups="pdk" />
+ <project path="external/ltp" name="platform/external/ltp" groups="vts,pdk" />
+ <project path="external/lz4" name="platform/external/lz4" groups="pdk" />
+ <project path="external/lzma" name="platform/external/lzma" groups="pdk" />
+ <project path="external/markdown" name="platform/external/markdown" groups="pdk" />
+ <project path="external/mdnsresponder" name="platform/external/mdnsresponder" groups="pdk" />
+ <project path="external/mesa3d" name="platform/external/mesa3d" groups="pdk-cw-fs,pdk-fs" />
+ <project path="external/Microsoft-GSL" name="platform/external/Microsoft-GSL" groups="pdk" />
+ <project path="external/minijail" name="platform/external/minijail" groups="pdk" />
+ <project path="external/mksh" name="platform/external/mksh" groups="pdk" />
+ <project path="external/mmc-utils" name="platform/external/mmc-utils" groups="pdk" />
+ <project path="external/mockftpserver" name="platform/external/mockftpserver" groups="pdk" />
+ <project path="external/mockito" name="platform/external/mockito" groups="pdk" />
+ <project path="external/mockwebserver" name="platform/external/mockwebserver" groups="pdk" />
+ <project path="external/modp_b64" name="platform/external/modp_b64" groups="pdk" />
+ <project path="external/mp4parser" name="platform/external/mp4parser" groups="pdk" />
+ <project path="external/mtpd" name="platform/external/mtpd" groups="pdk" />
+ <project path="external/nanohttpd" name="platform/external/nanohttpd" groups="pdk" />
+ <project path="external/nanopb-c" name="platform/external/nanopb-c" groups="pdk" />
+ <project path="external/naver-fonts" name="platform/external/naver-fonts" groups="pdk" />
+ <project path="external/neven" name="platform/external/neven" groups="pdk" />
+ <project path="external/nfacct" name="platform/external/nfacct" groups="pdk" />
+ <project path="external/nist-pkits" name="platform/external/nist-pkits" groups="pdk" />
+ <project path="external/nist-sip" name="platform/external/nist-sip" groups="pdk" />
+ <project path="external/noto-fonts" name="platform/external/noto-fonts" groups="pdk" />
+ <project path="external/oauth" name="platform/external/oauth" groups="pdk" />
+ <project path="external/objenesis" name="platform/external/objenesis" groups="pdk" />
+ <project path="external/oj-libjdwp" name="platform/external/oj-libjdwp" groups="pdk" />
+ <project path="external/okhttp" name="platform/external/okhttp" groups="pdk" />
+ <project path="external/one-true-awk" name="platform/external/one-true-awk" groups="pdk" />
+ <project path="external/opencv" name="platform/external/opencv" groups="pdk-cw-fs,pdk-fs" />
+ <project path="external/owasp/sanitizer" name="platform/external/owasp/sanitizer" groups="pdk" />
+ <project path="external/parameter-framework" name="platform/external/parameter-framework" groups="pdk" />
+ <project path="external/pcre" name="platform/external/pcre" groups="pdk" />
+ <project path="external/pdfium" name="platform/external/pdfium" groups="pdk" />
+ <project path="external/perf_data_converter" name="platform/external/perf_data_converter" groups="pdk" />
+ <project path="external/perfetto" name="platform/external/perfetto" groups="pdk" />
+ <project path="external/piex" name="platform/external/piex" groups="pdk" />
+ <project path="external/ply" name="platform/external/ply" groups="pdk" />
+ <project path="external/ppp" name="platform/external/ppp" groups="pdk" />
+ <project path="external/proguard" name="platform/external/proguard" groups="pdk" />
+ <project path="external/protobuf" name="platform/external/protobuf" groups="pdk" />
+ <project path="external/puffin" name="platform/external/puffin" groups="pdk" />
+ <project path="external/python/appdirs" name="platform/external/python/appdirs" groups="vts,pdk" />
+ <project path="external/python/cachetools" name="platform/external/python/cachetools" groups="vts,pdk" />
+ <project path="external/python/cpython2" name="platform/external/python/cpython2" groups="pdk" />
+ <project path="external/python/cpython3" name="platform/external/python/cpython3" groups="pdk" />
+ <project path="external/python/dateutil" name="platform/external/python/dateutil" groups="pdk" />
+ <project path="external/python/dill" name="platform/external/python/dill" groups="vts,pdk" />
+ <project path="external/python/enum" name="platform/external/python/enum" groups="vts,pdk" />
+ <project path="external/python/enum34" name="platform/external/python/enum34" groups="vts,pdk" />
+ <project path="external/python/future" name="platform/external/python/future" groups="vts,pdk" />
+ <project path="external/python/futures" name="platform/external/python/futures" groups="vts,pdk" />
+ <project path="external/python/gapic-google-cloud-pubsub-v1" name="platform/external/python/gapic-google-cloud-pubsub-v1" groups="vts,pdk" />
+ <project path="external/python/google-api-python-client" name="platform/external/python/google-api-python-client" groups="vts,pdk" />
+ <project path="external/python/google-auth" name="platform/external/python/google-auth" groups="vts,pdk" />
+ <project path="external/python/google-auth-httplib2" name="platform/external/python/google-auth-httplib2" groups="vts,pdk" />
+ <project path="external/python/google-cloud-core" name="platform/external/python/google-cloud-core" groups="vts,pdk" />
+ <project path="external/python/google-cloud-pubsub" name="platform/external/python/google-cloud-pubsub" groups="vts,pdk" />
+ <project path="external/python/google-gax" name="platform/external/python/google-gax" groups="vts,pdk" />
+ <project path="external/python/googleapis" name="platform/external/python/googleapis" groups="vts,pdk" />
+ <project path="external/python/grpc-google-iam-v1" name="platform/external/python/grpc-google-iam-v1" groups="vts,pdk" />
+ <project path="external/python/grpcio" name="platform/external/python/grpcio" groups="vts,pdk" />
+ <project path="external/python/httplib2" name="platform/external/python/httplib2" groups="vts,pdk" />
+ <project path="external/python/matplotlib" name="platform/external/python/matplotlib" groups="vts,pdk" />
+ <project path="external/python/numpy" name="platform/external/python/numpy" groups="vts,pdk" />
+ <project path="external/python/oauth2client" name="platform/external/python/oauth2client" groups="vts,pdk" />
+ <project path="external/python/olefile" name="platform/external/python/olefile" groups="vts,pdk" />
+ <project path="external/python/packaging" name="platform/external/python/packaging" groups="vts,pdk" />
+ <project path="external/python/parse" name="platform/external/python/parse" groups="vts,pdk" />
+ <project path="external/python/Pillow" name="platform/external/python/Pillow" groups="vts,pdk" />
+ <project path="external/python/ply" name="platform/external/python/ply" groups="vts,pdk" />
+ <project path="external/python/proto-google-cloud-pubsub-v1" name="platform/external/python/proto-google-cloud-pubsub-v1" groups="vts,pdk" />
+ <project path="external/python/protobuf" name="platform/external/python/protobuf" groups="vts,pdk" />
+ <project path="external/python/pyasn1" name="platform/external/python/pyasn1" groups="vts,pdk" />
+ <project path="external/python/pyasn1-modules" name="platform/external/python/pyasn1-modules" groups="vts,pdk" />
+ <project path="external/python/pyparsing" name="platform/external/python/pyparsing" groups="vts,pdk" />
+ <project path="external/python/requests" name="platform/external/python/requests" groups="vts,pdk" />
+ <project path="external/python/rsa" name="platform/external/python/rsa" groups="vts,pdk" />
+ <project path="external/python/scipy" name="platform/external/python/scipy" groups="vts,pdk" />
+ <project path="external/python/setuptools" name="platform/external/python/setuptools" groups="vts,pdk" />
+ <project path="external/python/six" name="platform/external/python/six" groups="vts,pdk" />
+ <project path="external/python/uritemplates" name="platform/external/python/uritemplates" groups="vts,pdk" />
+ <project path="external/replicaisland" name="platform/external/replicaisland" groups="pdk" />
+ <project path="external/rmi4utils" name="platform/external/rmi4utils" groups="pdk" />
+ <project path="external/robolectric" name="platform/external/robolectric" groups="pdk-cw-fs,pdk-fs" />
+ <project path="external/roboto-fonts" name="platform/external/roboto-fonts" groups="pdk" />
+ <project path="external/rootdev" name="platform/external/rootdev" groups="pdk" />
+ <project path="external/safe-iop" name="platform/external/safe-iop" groups="pdk" />
+ <project path="external/scapy" name="platform/external/scapy" groups="pdk-fs" />
+ <project path="external/scrypt" name="platform/external/scrypt" groups="pdk" />
+ <project path="external/seccomp-tests" name="platform/external/seccomp-tests" groups="pdk" />
+ <project path="external/selinux" name="platform/external/selinux" groups="pdk" />
+ <project path="external/sfntly" name="platform/external/sfntly" groups="pdk,qcom_msm8x26" />
+ <project path="external/shaderc/spirv-headers" name="platform/external/shaderc/spirv-headers" groups="pdk" />
+ <project path="external/shflags" name="platform/external/shflags" groups="pdk" />
+ <project path="external/skia" name="platform/external/skia" groups="pdk,qcom_msm8x26" />
+ <project path="external/sl4a" name="platform/external/sl4a" groups="pdk" />
+ <project path="external/slf4j" name="platform/external/slf4j" groups="pdk" />
+ <project path="external/smali" name="platform/external/smali" groups="pdk" />
+ <project path="external/snakeyaml" name="platform/external/snakeyaml" groups="pdk" />
+ <project path="external/sonic" name="platform/external/sonic" groups="pdk" />
+ <project path="external/sonivox" name="platform/external/sonivox" groups="pdk" />
+ <project path="external/speex" name="platform/external/speex" groups="pdk" />
+ <project path="external/spirv-llvm" name="platform/external/spirv-llvm" groups="pdk" />
+ <project path="external/sqlite" name="platform/external/sqlite" groups="pdk" />
+ <project path="external/squashfs-tools" name="platform/external/squashfs-tools" groups="pdk" />
+ <project path="external/strace" name="platform/external/strace" groups="pdk" />
+ <project path="external/stressapptest" name="platform/external/stressapptest" groups="pdk" />
+ <project path="external/subsampling-scale-image-view" name="platform/external/subsampling-scale-image-view" clone-depth="1" />
+ <project path="external/swiftshader" name="platform/external/swiftshader" groups="pdk" />
+ <project path="external/syslinux" name="platform/external/syslinux" groups="pdk" />
+ <project path="external/tagsoup" name="platform/external/tagsoup" groups="pdk" />
+ <project path="external/tcpdump" name="platform/external/tcpdump" groups="pdk" />
+ <project path="external/tensorflow" name="platform/external/tensorflow" groups="pdk" />
+ <project path="external/testng" name="platform/external/testng" groups="pdk" />
+ <project path="external/tinyalsa" name="platform/external/tinyalsa" groups="pdk" />
+ <project path="external/tinycompress" name="platform/external/tinycompress" groups="pdk" />
+ <project path="external/tinyxml" name="platform/external/tinyxml" groups="pdk" />
+ <project path="external/tinyxml2" name="platform/external/tinyxml2" groups="pdk" />
+ <project path="external/toolchain-utils" name="platform/external/toolchain-utils" />
+ <project path="external/toybox" name="platform/external/toybox" groups="pdk" />
+ <project path="external/tpm2" name="platform/external/tpm2" groups="pdk" />
+ <project path="external/trappy" name="platform/external/trappy" groups="pdk" />
+ <project path="external/tremolo" name="platform/external/tremolo" groups="pdk" />
+ <project path="external/turbine" name="platform/external/turbine" groups="pdk" />
+ <project path="external/unicode" name="platform/external/unicode" groups="pdk" />
+ <project path="external/universal-tween-engine" name="platform/external/universal-tween-engine" />
+ <project path="external/v4l2_codec2" name="platform/external/v4l2_codec2" groups="pdk" />
+ <project path="external/v8" name="platform/external/v8" groups="pdk" />
+ <project path="external/valgrind" name="platform/external/valgrind" groups="pdk" />
+ <project path="external/vboot_reference" name="platform/external/vboot_reference" groups="vboot,pdk-fs" />
+ <project path="external/vixl" name="platform/external/vixl" groups="pdk" />
+ <project path="external/vogar" name="platform/external/vogar" groups="pdk" />
+ <project path="external/volley" name="platform/external/volley" groups="pdk" />
+ <project path="external/vulkan-validation-layers" name="platform/external/vulkan-validation-layers" groups="pdk" />
+ <project path="external/walt" name="platform/external/walt" groups="pdk" />
+ <project path="external/webp" name="platform/external/webp" groups="pdk,qcom_msm8x26" />
+ <project path="external/webrtc" name="platform/external/webrtc" groups="pdk" />
+ <project path="external/webview_support_interfaces" name="platform/external/webview_support_interfaces" groups="pdk" />
+ <project path="external/wpa_supplicant_8" name="platform/external/wpa_supplicant_8" groups="pdk" />
+ <project path="external/wycheproof" name="platform/external/wycheproof" groups="pdk" />
+ <project path="external/x264" name="platform/external/x264" groups="pdk" />
+ <project path="external/xmlrpcpp" name="platform/external/xmlrpcpp" groups="pdk" />
+ <project path="external/xmp_toolkit" name="platform/external/xmp_toolkit" groups="pdk" />
+ <project path="external/xz-embedded" name="platform/external/xz-embedded" groups="pdk" />
+ <project path="external/zlib" name="platform/external/zlib" groups="pdk" />
+ <project path="external/zopfli" name="platform/external/zopfli" groups="pdk" />
+ <project path="external/zxing" name="platform/external/zxing" groups="pdk" />
+ <project path="frameworks/av" name="platform/frameworks/av" groups="pdk" />
+ <project path="frameworks/base" name="platform/frameworks/base" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/compile/libbcc" name="platform/frameworks/compile/libbcc" groups="pdk" />
+ <project path="frameworks/compile/mclinker" name="platform/frameworks/compile/mclinker" groups="pdk" />
+ <project path="frameworks/compile/slang" name="platform/frameworks/compile/slang" groups="pdk" />
+ <project path="frameworks/data-binding" name="platform/frameworks/data-binding" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/ex" name="platform/frameworks/ex" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/hardware/interfaces" name="platform/frameworks/hardware/interfaces" groups="pdk" />
+ <project path="frameworks/layoutlib" name="platform/frameworks/layoutlib" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/minikin" name="platform/frameworks/minikin" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/ml" name="platform/frameworks/ml" groups="pdk" />
+ <project path="frameworks/multidex" name="platform/frameworks/multidex" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/native" name="platform/frameworks/native" groups="pdk" />
+ <project path="frameworks/opt/bitmap" name="platform/frameworks/opt/bitmap" groups="pdk-fs" />
+ <project path="frameworks/opt/calendar" name="platform/frameworks/opt/calendar" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/car/services" name="platform/frameworks/opt/car/services" groups="pdk-fs" />
+ <project path="frameworks/opt/chips" name="platform/frameworks/opt/chips" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/colorpicker" name="platform/frameworks/opt/colorpicker" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/datetimepicker" name="platform/frameworks/opt/datetimepicker" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/inputmethodcommon" name="platform/frameworks/opt/inputmethodcommon" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/net/ethernet" name="platform/frameworks/opt/net/ethernet" groups="pdk-fs" />
+ <project path="frameworks/opt/net/ims" name="platform/frameworks/opt/net/ims" groups="frameworks_ims,pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/net/lowpan" name="platform/frameworks/opt/net/lowpan" groups="pdk-fs" />
+ <project path="frameworks/opt/net/voip" name="platform/frameworks/opt/net/voip" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/net/wifi" name="platform/frameworks/opt/net/wifi" groups="pdk" />
+ <project path="frameworks/opt/photoviewer" name="platform/frameworks/opt/photoviewer" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/setupwizard" name="platform/frameworks/opt/setupwizard" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/telephony" name="platform/frameworks/opt/telephony" groups="pdk" />
+ <project path="frameworks/opt/timezonepicker" name="platform/frameworks/opt/timezonepicker" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/opt/vcard" name="platform/frameworks/opt/vcard" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/rs" name="platform/frameworks/rs" groups="pdk" />
+ <project path="frameworks/support" name="platform/frameworks/support" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/webview" name="platform/frameworks/webview" groups="pdk-cw-fs,pdk-fs" />
+ <project path="frameworks/wilhelm" name="platform/frameworks/wilhelm" groups="pdk-cw-fs,pdk-fs" />
+ <project path="hardware/akm" name="platform/hardware/akm" groups="pdk" />
+ <project path="hardware/broadcom/libbt" name="platform/hardware/broadcom/libbt" groups="pdk" />
+ <project path="hardware/broadcom/wlan" name="platform/hardware/broadcom/wlan" groups="pdk,broadcom_wlan" />
+ <project path="hardware/google/apf" name="platform/hardware/google/apf" groups="pdk" />
+ <project path="hardware/google/easel" name="platform/hardware/google/easel" groups="pdk,easel" />
+ <project path="hardware/google/interfaces" name="platform/hardware/google/interfaces" groups="pdk" />
+ <project path="hardware/intel/audio_media" name="platform/hardware/intel/audio_media" groups="intel,pdk" />
+ <project path="hardware/intel/bootstub" name="platform/hardware/intel/bootstub" groups="intel,pdk" />
+ <project path="hardware/intel/common/libmix" name="platform/hardware/intel/common/libmix" groups="intel,pdk" />
+ <project path="hardware/intel/common/libstagefrighthw" name="platform/hardware/intel/common/libstagefrighthw" groups="intel,pdk" />
+ <project path="hardware/intel/common/libva" name="platform/hardware/intel/common/libva" groups="intel,pdk" />
+ <project path="hardware/intel/common/libwsbm" name="platform/hardware/intel/common/libwsbm" groups="intel,pdk" />
+ <project path="hardware/intel/common/omx-components" name="platform/hardware/intel/common/omx-components" groups="intel,pdk" />
+ <project path="hardware/intel/common/utils" name="platform/hardware/intel/common/utils" groups="intel,pdk" />
+ <project path="hardware/intel/common/wrs_omxil_core" name="platform/hardware/intel/common/wrs_omxil_core" groups="intel,pdk" />
+ <project path="hardware/intel/img/hwcomposer" name="platform/hardware/intel/img/hwcomposer" groups="intel,pdk" />
+ <project path="hardware/intel/img/psb_headers" name="platform/hardware/intel/img/psb_headers" groups="intel,pdk" />
+ <project path="hardware/intel/img/psb_video" name="platform/hardware/intel/img/psb_video" groups="intel,pdk" />
+ <project path="hardware/interfaces" name="platform/hardware/interfaces" groups="pdk" />
+ <project path="hardware/invensense" name="platform/hardware/invensense" groups="invensense,pdk" />
+ <project path="hardware/libhardware" name="platform/hardware/libhardware" groups="pdk" />
+ <project path="hardware/libhardware_legacy" name="platform/hardware/libhardware_legacy" groups="pdk" />
+ <project path="hardware/marvell/bt" name="platform/hardware/marvell/bt" groups="marvell_bt,pdk" />
+ <project path="hardware/nxp/nfc" name="platform/hardware/nxp/nfc" groups="pdk" />
+ <project path="hardware/nxp/secure_element" name="platform/hardware/nxp/secure_element" groups="pdk" />
+ <project path="hardware/qcom/audio" name="platform/hardware/qcom/audio" groups="qcom,qcom_audio,pdk" />
+ <project path="hardware/qcom/bootctrl" name="platform/hardware/qcom/bootctrl" groups="pdk" />
+ <project path="hardware/qcom/bt" name="platform/hardware/qcom/bt" groups="qcom,pdk" />
+ <project path="hardware/qcom/camera" name="platform/hardware/qcom/camera" groups="qcom_camera,pdk" />
+ <project path="hardware/qcom/data/ipacfg-mgr" name="platform/hardware/qcom/data/ipacfg-mgr" groups="qcom,pdk" />
+ <project path="hardware/qcom/display" name="platform/hardware/qcom/display" groups="pdk,qcom,qcom_display" />
+ <project path="hardware/qcom/gps" name="platform/hardware/qcom/gps" groups="qcom,qcom_gps,pdk" />
+ <project path="hardware/qcom/keymaster" name="platform/hardware/qcom/keymaster" groups="qcom,qcom_keymaster,pdk" />
+ <project path="hardware/qcom/media" name="platform/hardware/qcom/media" groups="qcom,pdk" />
+ <project path="hardware/qcom/msm8960" name="platform/hardware/qcom/msm8960" groups="qcom_msm8960,pdk" />
+ <project path="hardware/qcom/msm8994" name="platform/hardware/qcom/msm8994" groups="qcom_msm8994,pdk" />
+ <project path="hardware/qcom/msm8996" name="platform/hardware/qcom/msm8996" groups="qcom_msm8996,pdk" />
+ <project path="hardware/qcom/msm8998" name="platform/hardware/qcom/msm8998" groups="qcom_msm8998,pdk" />
+ <project path="hardware/qcom/msm8x09" name="platform/hardware/qcom/msm8x09" groups="qcom_msm8x09" />
+ <project path="hardware/qcom/msm8x26" name="platform/hardware/qcom/msm8x26" groups="qcom_msm8x26,pdk" />
+ <project path="hardware/qcom/msm8x27" name="platform/hardware/qcom/msm8x27" groups="qcom_msm8x27,pdk" />
+ <project path="hardware/qcom/msm8x84" name="platform/hardware/qcom/msm8x84" groups="qcom_msm8x84,pdk" />
+ <project path="hardware/qcom/neuralnetworks/hvxservice" name="platform/hardware/qcom/neuralnetworks/hvxservice" groups="wahoo" />
+ <project path="hardware/qcom/power" name="platform/hardware/qcom/power" groups="qcom,pdk" />
+ <project path="hardware/qcom/wlan" name="platform/hardware/qcom/wlan" groups="qcom_wlan,pdk" />
+ <project path="hardware/ril" name="platform/hardware/ril" groups="pdk" />
+ <project path="hardware/st/nfc" name="platform/hardware/st/nfc" groups="pdk" />
+ <project path="kernel/configs" name="kernel/configs" groups="vts,pdk" />
+ <project path="kernel/tests" name="kernel/tests" />
+ <project path="libcore" name="platform/libcore" groups="pdk" />
+ <project path="libnativehelper" name="platform/libnativehelper" groups="pdk" />
+ <project path="packages/apps/BasicSmsReceiver" name="platform/packages/apps/BasicSmsReceiver" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/Bluetooth" name="platform/packages/apps/Bluetooth" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/Browser2" name="platform/packages/apps/Browser2" groups="pdk-fs" />
+ <project path="packages/apps/Calendar" name="platform/packages/apps/Calendar" groups="pdk-fs" />
+ <project path="packages/apps/Camera2" name="platform/packages/apps/Camera2" groups="pdk-fs" />
+ <project path="packages/apps/Car/Dialer" name="platform/packages/apps/Car/Dialer" groups="pdk-fs" />
+ <project path="packages/apps/Car/Hvac" name="platform/packages/apps/Car/Hvac" groups="pdk-fs" />
+ <project path="packages/apps/Car/LatinIME" name="platform/packages/apps/Car/LatinIME" groups="pdk-fs" />
+ <project path="packages/apps/Car/Launcher" name="platform/packages/apps/Car/Launcher" groups="pdk-fs" />
+ <project path="packages/apps/Car/LensPicker" name="platform/packages/apps/Car/LensPicker" groups="pdk-fs" />
+ <project path="packages/apps/Car/libs" name="platform/packages/apps/Car/libs" groups="pdk-fs" />
+ <project path="packages/apps/Car/LocalMediaPlayer" name="platform/packages/apps/Car/LocalMediaPlayer" groups="pdk-fs" />
+ <project path="packages/apps/Car/Media" name="platform/packages/apps/Car/Media" groups="pdk-fs" />
+ <project path="packages/apps/Car/Messenger" name="platform/packages/apps/Car/Messenger" groups="pdk-fs" />
+ <project path="packages/apps/Car/Overview" name="platform/packages/apps/Car/Overview" groups="pdk-fs" />
+ <project path="packages/apps/Car/Radio" name="platform/packages/apps/Car/Radio" groups="pdk-fs" />
+ <project path="packages/apps/Car/Settings" name="platform/packages/apps/Car/Settings" groups="pdk-fs" />
+ <project path="packages/apps/Car/Stream" name="platform/packages/apps/Car/Stream" groups="pdk-fs" />
+ <project path="packages/apps/Car/SystemUpdater" name="platform/packages/apps/Car/SystemUpdater" groups="pdk-fs" />
+ <project path="packages/apps/CarrierConfig" name="platform/packages/apps/CarrierConfig" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/CellBroadcastReceiver" name="platform/packages/apps/CellBroadcastReceiver" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/CertInstaller" name="platform/packages/apps/CertInstaller" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/Contacts" name="platform/packages/apps/Contacts" groups="pdk-fs" />
+ <project path="packages/apps/DeskClock" name="platform/packages/apps/DeskClock" groups="pdk-fs" />
+ <project path="packages/apps/DevCamera" name="platform/packages/apps/DevCamera" groups="pdk" />
+ <project path="packages/apps/Dialer" name="platform/packages/apps/Dialer" groups="pdk-fs" />
+ <project path="packages/apps/DocumentsUI" name="platform/packages/apps/DocumentsUI" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/Email" name="platform/packages/apps/Email" groups="pdk-fs" />
+ <project path="packages/apps/EmergencyInfo" name="platform/packages/apps/EmergencyInfo" groups="pdk-fs" />
+ <project path="packages/apps/ExactCalculator" name="platform/packages/apps/ExactCalculator" groups="pdk-fs" />
+ <project path="packages/apps/Gallery" name="platform/packages/apps/Gallery" groups="pdk-fs" />
+ <project path="packages/apps/Gallery2" name="platform/packages/apps/Gallery2" groups="pdk-fs" />
+ <project path="packages/apps/HTMLViewer" name="platform/packages/apps/HTMLViewer" groups="pdk-fs" />
+ <project path="packages/apps/KeyChain" name="platform/packages/apps/KeyChain" groups="pdk-fs" />
+ <project path="packages/apps/Launcher2" name="platform/packages/apps/Launcher2" groups="pdk-fs" />
+ <project path="packages/apps/Launcher3" name="platform/packages/apps/Launcher3" groups="pdk-fs" />
+ <project path="packages/apps/LegacyCamera" name="platform/packages/apps/LegacyCamera" groups="pdk-fs" />
+ <project path="packages/apps/ManagedProvisioning" name="platform/packages/apps/ManagedProvisioning" groups="pdk-fs" />
+ <project path="packages/apps/Messaging" name="platform/packages/apps/Messaging" groups="pdk-fs" />
+ <project path="packages/apps/Music" name="platform/packages/apps/Music" groups="pdk-fs" />
+ <project path="packages/apps/MusicFX" name="platform/packages/apps/MusicFX" groups="pdk-fs" />
+ <project path="packages/apps/Nfc" name="platform/packages/apps/Nfc" groups="apps_nfc,pdk-fs" />
+ <project path="packages/apps/OneTimeInitializer" name="platform/packages/apps/OneTimeInitializer" groups="pdk-fs" />
+ <project path="packages/apps/PackageInstaller" name="platform/packages/apps/PackageInstaller" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/PhoneCommon" name="platform/packages/apps/PhoneCommon" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/apps/Protips" name="platform/packages/apps/Protips" groups="pdk-fs" />
+ <project path="packages/apps/Provision" name="platform/packages/apps/Provision" groups="pdk-fs" />
+ <project path="packages/apps/QuickSearchBox" name="platform/packages/apps/QuickSearchBox" groups="pdk-fs" />
+ <project path="packages/apps/SafetyRegulatoryInfo" name="platform/packages/apps/SafetyRegulatoryInfo" groups="pdk-fs" />
+ <project path="packages/apps/SecureElement" name="platform/packages/apps/SecureElement" groups="apps_se,pdk-fs" />
+ <project path="packages/apps/Settings" name="platform/packages/apps/Settings" groups="pdk-fs" />
+ <project path="packages/apps/SoundRecorder" name="platform/packages/apps/SoundRecorder" groups="pdk-fs" />
+ <project path="packages/apps/SpareParts" name="platform/packages/apps/SpareParts" groups="pdk-fs" />
+ <project path="packages/apps/Stk" name="platform/packages/apps/Stk" groups="apps_stk,pdk-fs" />
+ <project path="packages/apps/StorageManager" name="platform/packages/apps/StorageManager" groups="pdk-fs" />
+ <project path="packages/apps/Tag" name="platform/packages/apps/Tag" groups="pdk-fs" />
+ <project path="packages/apps/Terminal" name="platform/packages/apps/Terminal" groups="pdk-fs" />
+ <project path="packages/apps/Test/connectivity" name="platform/packages/apps/Test/connectivity" groups="pdk" />
+ <project path="packages/apps/TimeZoneData" name="platform/packages/apps/TimeZoneData" groups="pdk" />
+ <project path="packages/apps/TimeZoneUpdater" name="platform/packages/apps/TimeZoneUpdater" groups="pdk" />
+ <project path="packages/apps/Traceur" name="platform/packages/apps/Traceur" groups="pdk-fs" />
+ <project path="packages/apps/TvSettings" name="platform/packages/apps/TvSettings" groups="pdk-fs" />
+ <project path="packages/apps/TV" name="platform/packages/apps/TV" />
+ <project path="packages/apps/UnifiedEmail" name="platform/packages/apps/UnifiedEmail" groups="pdk-fs" />
+ <project path="packages/apps/WallpaperPicker" name="platform/packages/apps/WallpaperPicker" groups="pdk-fs" />
+ <project path="packages/experimental" name="platform/packages/experimental" />
+ <project path="packages/inputmethods/LatinIME" name="platform/packages/inputmethods/LatinIME" groups="pdk-fs" />
+ <project path="packages/inputmethods/OpenWnn" name="platform/packages/inputmethods/OpenWnn" groups="pdk-fs" />
+ <project path="packages/providers/ApplicationsProvider" name="platform/packages/providers/ApplicationsProvider" groups="pdk-fs" />
+ <project path="packages/providers/BlockedNumberProvider" name="platform/packages/providers/BlockedNumberProvider" groups="pdk-fs" />
+ <project path="packages/providers/BookmarkProvider" name="platform/packages/providers/BookmarkProvider" groups="pdk-fs" />
+ <project path="packages/providers/CalendarProvider" name="platform/packages/providers/CalendarProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/providers/CallLogProvider" name="platform/packages/providers/CallLogProvider" groups="pdk-fs" />
+ <project path="packages/providers/ContactsProvider" name="platform/packages/providers/ContactsProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/providers/DownloadProvider" name="platform/packages/providers/DownloadProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/providers/MediaProvider" name="platform/packages/providers/MediaProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/providers/PartnerBookmarksProvider" name="platform/packages/providers/PartnerBookmarksProvider" groups="pdk-fs" />
+ <project path="packages/providers/TelephonyProvider" name="platform/packages/providers/TelephonyProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/providers/TvProvider" name="platform/packages/providers/TvProvider" groups="pdk-fs" />
+ <project path="packages/providers/UserDictionaryProvider" name="platform/packages/providers/UserDictionaryProvider" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/screensavers/Basic" name="platform/packages/screensavers/Basic" groups="pdk-fs" />
+ <project path="packages/screensavers/PhotoTable" name="platform/packages/screensavers/PhotoTable" groups="pdk-fs" />
+ <project path="packages/screensavers/WebView" name="platform/packages/screensavers/WebView" groups="pdk-fs" />
+ <project path="packages/services/BuiltInPrintService" name="platform/packages/services/BuiltInPrintService" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/services/Car" name="platform/packages/services/Car" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/services/Mms" name="platform/packages/services/Mms" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/services/NetworkRecommendation" name="platform/packages/services/NetworkRecommendation" groups="pdk-fs" />
+ <project path="packages/services/Telecomm" name="platform/packages/services/Telecomm" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/services/Telephony" name="platform/packages/services/Telephony" groups="pdk-cw-fs,pdk-fs" />
+ <project path="packages/wallpapers/LivePicker" name="platform/packages/wallpapers/LivePicker" groups="pdk-fs" />
+ <project path="pdk" name="platform/pdk" groups="pdk" />
+ <project path="platform_testing" name="platform/platform_testing" groups="pdk-fs,pdk-cw-fs,cts" />
+ <project path="prebuilts/abi-dumps/ndk" name="platform/prebuilts/abi-dumps/ndk" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/abi-dumps/vndk" name="platform/prebuilts/abi-dumps/vndk" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/android-emulator" name="platform/prebuilts/android-emulator" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/checkstyle" name="platform/prebuilts/checkstyle" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/clang/host/darwin-x86" name="platform/prebuilts/clang/host/darwin-x86" groups="pdk,darwin" clone-depth="1" />
+ <project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/deqp" name="platform/prebuilts/deqp" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/devtools" name="platform/prebuilts/devtools" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/gcc/darwin-x86/aarch64/aarch64-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/aarch64/aarch64-linux-android-4.9" groups="pdk,darwin,arm" clone-depth="1" />
+ <project path="prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9" name="platform/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9" groups="pdk,darwin,arm" clone-depth="1" />
+ <project path="prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" name="platform/prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" groups="pdk,darwin" clone-depth="1" />
+ <project path="prebuilts/gcc/darwin-x86/mips/mips64el-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/mips/mips64el-linux-android-4.9" groups="pdk,darwin,mips" clone-depth="1" />
+ <project path="prebuilts/gcc/darwin-x86/x86/x86_64-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/x86/x86_64-linux-android-4.9" groups="pdk,darwin,x86" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9" groups="pdk,linux,arm" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" groups="pdk,linux,arm" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8" groups="pdk,linux" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8" groups="pdk-fs" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/mips/mips64el-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/mips/mips64el-linux-android-4.9" groups="pdk,linux,mips" clone-depth="1" />
+ <project path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" groups="pdk,linux,x86" clone-depth="1" />
+ <project path="prebuilts/gdb/darwin-x86" name="platform/prebuilts/gdb/darwin-x86" groups="darwin" clone-depth="1" />
+ <project path="prebuilts/gdb/linux-x86" name="platform/prebuilts/gdb/linux-x86" groups="linux" clone-depth="1" />
+ <project path="prebuilts/go/darwin-x86" name="platform/prebuilts/go/darwin-x86" groups="darwin,pdk,tradefed" clone-depth="1" />
+ <project path="prebuilts/go/linux-x86" name="platform/prebuilts/go/linux-x86" groups="linux,pdk,tradefed" clone-depth="1" />
+ <project path="prebuilts/gradle-plugin" name="platform/prebuilts/gradle-plugin" groups="pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/jdk/jdk8" name="platform/prebuilts/jdk/jdk8" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/jdk/jdk9" name="platform/prebuilts/jdk/jdk9" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/libs/libedit" name="platform/prebuilts/libs/libedit" groups="pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/maven_repo/android" name="platform/prebuilts/maven_repo/android" groups="pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/maven_repo/bumptech" name="platform/prebuilts/maven_repo/bumptech" groups="pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/maven_repo/google-play-service-client-libraries-3p" name="platform/prebuilts/maven_repo/google-play-service-client-libraries-3p" clone-depth="1" />
+ <project path="prebuilts/misc" name="platform/prebuilts/misc" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/ndk" name="platform/prebuilts/ndk" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/python/darwin-x86/2.7.5" name="platform/prebuilts/python/darwin-x86/2.7.5" groups="darwin,pdk,pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/python/linux-x86/2.7.5" name="platform/prebuilts/python/linux-x86/2.7.5" groups="linux,pdk,pdk-cw-fs,pdk-fs" clone-depth="1" />
+ <project path="prebuilts/qemu-kernel" name="platform/prebuilts/qemu-kernel" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/r8" name="platform/prebuilts/r8" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/sdk" name="platform/prebuilts/sdk" groups="pdk" clone-depth="1" />
+ <project path="prebuilts/tools" name="platform/prebuilts/tools" groups="pdk,tools" clone-depth="1" />
+ <project path="sdk" name="platform/sdk" groups="pdk-cw-fs,pdk-fs" />
+ <project path="system/bt" name="platform/system/bt" groups="pdk" />
+ <project path="system/ca-certificates" name="platform/system/ca-certificates" groups="pdk" />
+ <project path="system/chre" name="platform/system/chre" groups="pdk" />
+ <project path="system/connectivity/wificond" name="platform/system/connectivity/wificond" groups="pdk" />
+ <project path="system/connectivity/wifilogd" name="platform/system/connectivity/wifilogd" groups="pdk" />
+ <project path="system/core" name="platform/system/core" groups="pdk" />
+ <project path="system/extras" name="platform/system/extras" groups="pdk" />
+ <project path="system/gatekeeper" name="platform/system/gatekeeper" groups="pdk" />
+ <project path="system/hardware/interfaces" name="platform/system/hardware/interfaces" groups="pdk" />
+ <project path="system/hwservicemanager" name="platform/system/hwservicemanager" groups="pdk" />
+ <project path="system/iot/attestation" name="platform/system/iot/attestation" groups="pdk" />
+ <project path="system/keymaster" name="platform/system/keymaster" groups="pdk" />
+ <project path="system/libfmq" name="platform/system/libfmq" groups="pdk" />
+ <project path="system/libhidl" name="platform/system/libhidl" groups="pdk" />
+ <project path="system/libhwbinder" name="platform/system/libhwbinder" groups="pdk" />
+ <project path="system/libufdt" name="platform/system/libufdt" groups="pdk" />
+ <project path="system/libvintf" name="platform/system/libvintf" groups="pdk" />
+ <project path="system/media" name="platform/system/media" groups="pdk" />
+ <project path="system/netd" name="platform/system/netd" groups="pdk" />
+ <project path="system/nfc" name="platform/system/nfc" groups="pdk" />
+ <project path="system/nvram" name="platform/system/nvram" groups="pdk" />
+ <project path="system/security" name="platform/system/security" groups="pdk" />
+ <project path="system/sepolicy" name="platform/system/sepolicy" groups="pdk" />
+ <project path="system/timezone" name="platform/system/timezone" groups="pdk" />
+ <project path="system/tools/aidl" name="platform/system/tools/aidl" groups="pdk-cw-fs,pdk-fs" />
+ <project path="system/tools/bpt" name="platform/system/tools/bpt" groups="pdk" />
+ <project path="system/tools/hidl" name="platform/system/tools/hidl" groups="pdk" />
+ <project path="system/update_engine" name="platform/system/update_engine" groups="pdk" />
+ <project path="system/vold" name="platform/system/vold" groups="pdk" />
+ <project path="test/framework" name="platform/test/framework" groups="vts,pdk" />
+ <project path="test/mlts/benchmark" name="platform/test/mlts/benchmark" groups="pdk" />
+ <project path="test/mlts/models" name="platform/test/mlts/models" groups="pdk" />
+ <project path="test/sts" name="platform/test/sts" groups="sts,pdk" />
+ <project path="test/vti/alert" name="platform/test/vti/alert" groups="vts,pdk" />
+ <project path="test/vti/dashboard" name="platform/test/vti/dashboard" groups="vts,pdk" />
+ <project path="test/vti/fuzz_test_serving" name="platform/test/vti/fuzz_test_serving" groups="vts,pdk" />
+ <project path="test/vti/test_serving" name="platform/test/vti/test_serving" groups="vts,pdk" />
+ <project path="test/vts" name="platform/test/vts" groups="vts,pdk" />
+ <project path="test/vts-testcase/fuzz" name="platform/test/vts-testcase/fuzz" groups="vts,pdk" />
+ <project path="test/vts-testcase/hal" name="platform/test/vts-testcase/hal" groups="vts,pdk" />
+ <project path="test/vts-testcase/hal-trace" name="platform/test/vts-testcase/hal-trace" groups="vts,pdk" />
+ <project path="test/vts-testcase/kernel" name="platform/test/vts-testcase/kernel" groups="vts,pdk" />
+ <project path="test/vts-testcase/nbu" name="platform/test/vts-testcase/nbu" groups="vts,pdk" />
+ <project path="test/vts-testcase/performance" name="platform/test/vts-testcase/performance" groups="vts,pdk" />
+ <project path="test/vts-testcase/security" name="platform/test/vts-testcase/security" groups="vts,pdk" />
+ <project path="test/vts-testcase/vndk" name="platform/test/vts-testcase/vndk" groups="vts,pdk" />
+ <project path="toolchain/benchmark" name="toolchain/benchmark" />
+ <project path="toolchain/binutils" name="toolchain/binutils" groups="pdk" />
+ <project path="toolchain/pgo-profiles" name="toolchain/pgo-profiles" groups="pdk" />
+ <project path="tools/acloud" name="platform/tools/acloud" groups="tools,vts,pdk,tradefed" />
+ <project path="tools/adt/idea" name="platform/tools/adt/idea" groups="notdefault,tools" />
+ <project path="tools/apksig" name="platform/tools/apksig" groups="pdk,tradefed" />
+ <project path="tools/apkzlib" name="platform/tools/apkzlib" groups="pdk,tradefed" />
+ <project path="tools/base" name="platform/tools/base" groups="notdefault,tools" />
+ <project path="tools/build" name="platform/tools/build" groups="notdefault,tools" />
+ <project path="tools/dexter" name="platform/tools/dexter" groups="tools,pdk-fs" />
+ <project path="tools/external/fat32lib" name="platform/tools/external/fat32lib" groups="tools" />
+ <project path="tools/external/gradle" name="platform/tools/external/gradle" groups="tools" clone-depth="1" />
+ <project path="tools/idea" name="platform/tools/idea" groups="notdefault,tools" />
+ <project path="tools/loganalysis" name="platform/tools/loganalysis" groups="nopresubmit,pdk,tradefed" />
+ <project path="tools/metalava" name="platform/tools/metalava" groups="tools" />
+ <project path="tools/motodev" name="platform/tools/motodev" groups="notdefault,motodev" />
+ <project path="tools/repohooks" name="platform/tools/repohooks" groups="adt-infra,cts,developers,motodev,pdk,tools,tradefed" />
+ <project path="tools/security" name="platform/tools/security" groups="pdk,tools" />
+ <project path="tools/studio/cloud" name="platform/tools/studio/cloud" groups="notdefault,tools" />
+ <project path="tools/swt" name="platform/tools/swt" groups="notdefault,tools" />
+ <project path="tools/test/connectivity" name="platform/tools/test/connectivity" groups="pdk" />
+ <project path="tools/test/graphicsbenchmark" name="platform/tools/test/graphicsbenchmark" groups="pdk" />
+ <project path="tools/tradefederation/core" name="platform/tools/tradefederation" groups="pdk,tradefed" />
+ <project path="tools/tradefederation/contrib" name="platform/tools/tradefederation/contrib" groups="pdk,tradefed" />
+
+ <repo-hooks in-project="platform/tools/repohooks" enabled-list="pre-upload" />
+
+</manifest>
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 460d3b6a7e4..343e140f5fb 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -28,6 +28,16 @@ describe NamespacesHelper do
expect(options).not_to include(admin_group.name)
expect(options).to include(user_group.name)
+ expect(options).to include(user.name)
+ end
+
+ it 'returns only groups if groups_only option is true' do
+ allow(helper).to receive(:current_user).and_return(user)
+
+ options = helper.namespaces_options(nil, groups_only: true)
+
+ expect(options).not_to include(user.name)
+ expect(options).to include(user_group.name)
end
context 'when nested groups are available', :nested_groups do
diff --git a/spec/javascripts/frequent_items/store/actions_spec.js b/spec/javascripts/frequent_items/store/actions_spec.js
index 0cdd033d38f..0a8525e77d6 100644
--- a/spec/javascripts/frequent_items/store/actions_spec.js
+++ b/spec/javascripts/frequent_items/store/actions_spec.js
@@ -205,7 +205,7 @@ describe('Frequent Items Dropdown Store Actions', () => {
actions.setSearchQuery,
{ query: 'test' },
mockedState,
- [{ type: types.SET_SEARCH_QUERY }],
+ [{ type: types.SET_SEARCH_QUERY, payload: { query: 'test' } }],
[{ type: 'fetchSearchedItems', payload: { query: 'test' } }],
done,
);
@@ -216,7 +216,7 @@ describe('Frequent Items Dropdown Store Actions', () => {
actions.setSearchQuery,
null,
mockedState,
- [{ type: types.SET_SEARCH_QUERY }],
+ [{ type: types.SET_SEARCH_QUERY, payload: null }],
[{ type: 'fetchFrequentItems' }],
done,
);
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index d6ab0aeeed7..4ca7015184e 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -1,71 +1,103 @@
+const noop = () => {};
+
/**
- * helper for testing action with expected mutations inspired in
+ * Helper for testing action with expected mutations inspired in
* https://vuex.vuejs.org/en/testing.html
*
+ * @param {Function} action to be tested
+ * @param {Object} payload will be provided to the action
+ * @param {Object} state will be provided to the action
+ * @param {Array} [expectedMutations=[]] mutations expected to be committed
+ * @param {Array} [expectedActions=[]] actions expected to be dispatched
+ * @param {Function} [done=noop] to be executed after the tests
+ * @return {Promise}
+ *
* @example
* testAction(
* actions.actionName, // action
- * { }, // mocked response
- * state, // state
+ * { }, // mocked payload
+ * state, //state
+ * // expected mutations
* [
* { type: types.MUTATION}
- * { type: types.MUTATION_1, payload: {}}
- * ], // mutations
+ * { type: types.MUTATION_1, payload: jasmine.any(Number)}
+ * ],
+ * // expected actions
* [
- * { type: 'actionName', payload: {}},
- * { type: 'actionName1', payload: {}}
- * ] //actions
+ * { type: 'actionName', payload: {param: 'foobar'}},
+ * { type: 'actionName1'}
+ * ]
* done,
* );
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked payload
+ * state, //state
+ * [ { type: types.MUTATION} ], // expected mutations
+ * [], // expected actions
+ * ).then(done)
+ * .catch(done.fail);
*/
-export default (action, payload, state, expectedMutations, expectedActions, done) => {
- let mutationsCount = 0;
- let actionsCount = 0;
+export default (
+ action,
+ payload,
+ state,
+ expectedMutations = [],
+ expectedActions = [],
+ done = noop,
+) => {
+ const mutations = [];
+ const actions = [];
// mock commit
const commit = (type, mutationPayload) => {
- const mutation = expectedMutations[mutationsCount];
-
- expect(mutation.type).toEqual(type);
+ const mutation = { type };
- if (mutation.payload) {
- expect(mutation.payload).toEqual(mutationPayload);
+ if (typeof mutationPayload !== 'undefined') {
+ mutation.payload = mutationPayload;
}
- mutationsCount += 1;
- if (mutationsCount >= expectedMutations.length) {
- done();
- }
+ mutations.push(mutation);
};
// mock dispatch
const dispatch = (type, actionPayload) => {
- const actionExpected = expectedActions[actionsCount];
-
- expect(actionExpected.type).toEqual(type);
+ const dispatchedAction = { type };
- if (actionExpected.payload) {
- expect(actionExpected.payload).toEqual(actionPayload);
+ if (typeof actionPayload !== 'undefined') {
+ dispatchedAction.payload = actionPayload;
}
- actionsCount += 1;
- if (actionsCount >= expectedActions.length) {
- done();
- }
+ actions.push(dispatchedAction);
};
- // call the action with mocked store and arguments
- action({ commit, state, dispatch, rootState: state }, payload);
-
- // check if no mutations should have been dispatched
- if (expectedMutations.length === 0) {
- expect(mutationsCount).toEqual(0);
+ const validateResults = () => {
+ expect({
+ mutations,
+ actions,
+ }).toEqual({
+ mutations: expectedMutations,
+ actions: expectedActions,
+ });
done();
- }
+ };
- // check if no mutations should have been dispatched
- if (expectedActions.length === 0) {
- expect(actionsCount).toEqual(0);
- done();
- }
+ return new Promise((resolve, reject) => {
+ try {
+ const result = action({ commit, state, dispatch, rootState: state }, payload);
+ resolve(result);
+ } catch (e) {
+ reject(e);
+ }
+ })
+ .catch(error => {
+ validateResults();
+ throw error;
+ })
+ .then(data => {
+ validateResults();
+ return data;
+ });
};
diff --git a/spec/javascripts/helpers/vuex_action_helper_spec.js b/spec/javascripts/helpers/vuex_action_helper_spec.js
new file mode 100644
index 00000000000..8d6ad6750c0
--- /dev/null
+++ b/spec/javascripts/helpers/vuex_action_helper_spec.js
@@ -0,0 +1,141 @@
+import MockAdapter from 'axios-mock-adapter';
+import { TEST_HOST } from 'spec/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import testAction from './vuex_action_helper';
+
+describe('VueX test helper (testAction)', () => {
+ let originalExpect;
+ let assertion;
+ let mock;
+ const noop = () => {};
+
+ beforeAll(() => {
+ mock = new MockAdapter(axios);
+ /*
+ In order to test the helper properly, we need to overwrite the jasmine `expect` helper.
+ We test that the testAction helper properly passes the dispatched actions/committed mutations
+ to the jasmine helper.
+ */
+ originalExpect = expect;
+ assertion = null;
+ global.expect = actual => ({
+ toEqual: () => {
+ originalExpect(actual).toEqual(assertion);
+ },
+ });
+ });
+
+ afterAll(() => {
+ mock.restore();
+ global.expect = originalExpect;
+ });
+
+ it('should properly pass on state and payload', () => {
+ const exampleState = { FOO: 12, BAR: 3 };
+ const examplePayload = { BAZ: 73, BIZ: 55 };
+
+ const action = ({ state }, payload) => {
+ originalExpect(state).toEqual(exampleState);
+ originalExpect(payload).toEqual(examplePayload);
+ };
+
+ assertion = { mutations: [], actions: [] };
+
+ testAction(action, examplePayload, exampleState);
+ });
+
+ describe('should work with synchronous actions', () => {
+ it('committing mutation', () => {
+ const action = ({ commit }) => {
+ commit('MUTATION');
+ };
+
+ assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
+
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
+
+ it('dispatching action', () => {
+ const action = ({ dispatch }) => {
+ dispatch('ACTION');
+ };
+
+ assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
+
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
+
+ it('work with jasmine done once finished', done => {
+ assertion = { mutations: [], actions: [] };
+
+ testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('provide promise interface', done => {
+ assertion = { mutations: [], actions: [] };
+
+ testAction(noop, null, {}, assertion.mutations, assertion.actions)
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('should work with promise based actions (fetch action)', () => {
+ let lastError;
+ const data = { FOO: 'BAR' };
+
+ const promiseAction = ({ commit, dispatch }) => {
+ dispatch('ACTION');
+
+ return axios
+ .get(TEST_HOST)
+ .catch(error => {
+ commit('ERROR');
+ lastError = error;
+ throw error;
+ })
+ .then(() => {
+ commit('SUCCESS');
+ return data;
+ });
+ };
+
+ beforeEach(() => {
+ lastError = null;
+ });
+
+ it('work with jasmine done once finished', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
+
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('return original data of successful promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
+
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
+ .then(res => {
+ originalExpect(res).toEqual(data);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('return original error of rejected promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(500, '');
+
+ assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(promiseAction, null, {}, assertion.mutations, assertion.actions)
+ .then(done.fail)
+ .catch(error => {
+ originalExpect(error).toBe(lastError);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js
index 770dca9cb0f..9895682f388 100644
--- a/spec/javascripts/ide/components/ide_status_bar_spec.js
+++ b/spec/javascripts/ide/components/ide_status_bar_spec.js
@@ -13,6 +13,7 @@ describe('ideStatusBar', () => {
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
+ store.state.currentBranchId = 'master';
vm = createComponentWithStore(Component, store).$mount();
});
@@ -60,4 +61,29 @@ describe('ideStatusBar', () => {
expect(vm.getCommitPath('abc123de')).toBe('/commit/abc123de');
});
});
+
+ describe('pipeline status', () => {
+ it('opens right sidebar on clicking icon', done => {
+ spyOn(vm, 'setRightPane');
+ Vue.set(vm.$store.state.pipelines, 'latestPipeline', {
+ details: {
+ status: {
+ text: 'success',
+ details_path: 'test',
+ icon: 'success',
+ },
+ },
+ });
+
+ vm
+ .$nextTick()
+ .then(() => {
+ vm.$el.querySelector('.ide-status-pipeline button').click();
+
+ expect(vm.setRightPane).toHaveBeenCalledWith('pipelines-list');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 80bf664d491..4bbda4c8e80 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -9,6 +9,9 @@ export const projectData = {
master: {
treeId: 'abcproject/master',
can_push: true,
+ commit: {
+ id: '123',
+ },
},
},
mergeRequests: {},
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 58d3ffc6d94..f570c0b16bd 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -601,10 +601,7 @@ describe('IDE store file actions', () => {
actions.unstageChange,
'path',
store.state,
- [
- { type: types.UNSTAGE_CHANGE, payload: 'path' },
- { type: types.SET_LAST_COMMIT_MSG, payload: '' },
- ],
+ [{ type: types.UNSTAGE_CHANGE, payload: 'path' }],
[],
done,
);
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index ca79edafb7e..6a85968e199 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -73,6 +73,7 @@ describe('IDE store project actions', () => {
branchId: store.state.currentBranchId,
},
store.state,
+ // mutations
[
{
type: 'SET_BRANCH_COMMIT',
@@ -82,17 +83,9 @@ describe('IDE store project actions', () => {
commit: { id: '123' },
},
},
- ], // mutations
- [
- {
- type: 'getLastCommitPipeline',
- payload: {
- projectId: 'abc/def',
- projectIdNumber: store.state.projects['abc/def'].id,
- branchId: 'master',
- },
- },
- ], // action
+ ],
+ // action
+ [],
done,
);
});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index 6860e6cdb91..9f098eded08 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -192,11 +192,8 @@ describe('Multi-file store tree actions', () => {
showTreeEntry,
'grandparent/parent/child.txt',
store.state,
- [
- { type: types.SET_TREE_OPEN, payload: 'grandparent/parent' },
- { type: types.SET_TREE_OPEN, payload: 'grandparent' },
- ],
- [{ type: 'showTreeEntry' }],
+ [{ type: types.SET_TREE_OPEN, payload: 'grandparent/parent' }],
+ [{ type: 'showTreeEntry', payload: 'grandparent/parent' }],
done,
);
});
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
index d21f33eaf6d..d063f1ea860 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
@@ -122,21 +122,6 @@ describe('IDE merge requests actions', () => {
});
});
- it('dispatches request', done => {
- testAction(
- fetchMergeRequests,
- { type: 'created' },
- mockedState,
- [],
- [
- { type: 'requestMergeRequests' },
- { type: 'resetMergeRequests' },
- { type: 'receiveMergeRequestsSuccess' },
- ],
- done,
- );
- });
-
it('dispatches success with received data', done => {
testAction(
fetchMergeRequests,
@@ -144,8 +129,8 @@ describe('IDE merge requests actions', () => {
mockedState,
[],
[
- { type: 'requestMergeRequests' },
- { type: 'resetMergeRequests' },
+ { type: 'requestMergeRequests', payload: 'created' },
+ { type: 'resetMergeRequests', payload: 'created' },
{
type: 'receiveMergeRequestsSuccess',
payload: { type: 'created', data: mergeRequests },
@@ -168,9 +153,9 @@ describe('IDE merge requests actions', () => {
mockedState,
[],
[
- { type: 'requestMergeRequests' },
- { type: 'resetMergeRequests' },
- { type: 'receiveMergeRequestsError' },
+ { type: 'requestMergeRequests', payload: 'created' },
+ { type: 'resetMergeRequests', payload: 'created' },
+ { type: 'receiveMergeRequestsError', payload: { type: 'created', search: '' } },
],
done,
);
diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
index 836ba72b5d8..91edb388791 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -315,7 +315,7 @@ describe('IDE pipelines actions', () => {
'job',
mockedState,
[{ type: types.SET_DETAIL_JOB, payload: 'job' }],
- [{ type: 'setRightPane' }],
+ [{ type: 'setRightPane', payload: 'jobs-detail' }],
done,
);
});
@@ -325,7 +325,7 @@ describe('IDE pipelines actions', () => {
setDetailJob,
null,
mockedState,
- [{ type: types.SET_DETAIL_JOB }],
+ [{ type: types.SET_DETAIL_JOB, payload: null }],
[{ type: 'setRightPane', payload: rightSidebarViews.pipelines }],
done,
);
@@ -336,7 +336,7 @@ describe('IDE pipelines actions', () => {
setDetailJob,
'job',
mockedState,
- [{ type: types.SET_DETAIL_JOB }],
+ [{ type: types.SET_DETAIL_JOB, payload: 'job' }],
[{ type: 'setRightPane', payload: rightSidebarViews.jobsDetail }],
done,
);
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 0eff98bcc9d..a0ca8f8b4c3 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -166,13 +166,13 @@ if (process.env.BABEL_ENV === 'coverage') {
];
describe('Uncovered files', function() {
- const sourceFiles = require.context('~', true, /\.js$/);
+ const sourceFiles = require.context('~', true, /\.(js|vue)$/);
$.holdReady(true);
sourceFiles.keys().forEach(function(path) {
// ignore if there is a matching spec file
- if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) {
+ if (testsContext.keys().indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) {
return;
}
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f155ebc9d1a..64b08dd9c4b 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1187,50 +1187,17 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#find_branch' do
- shared_examples 'finding a branch' do
- it 'should return a Branch for master' do
- branch = repository.find_branch('master')
+ it 'should return a Branch for master' do
+ branch = repository.find_branch('master')
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
- end
-
- it 'should handle non-existent branch' do
- branch = repository.find_branch('this-is-garbage')
-
- expect(branch).to eq(nil)
- end
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
end
- context 'when Gitaly find_branch feature is enabled' do
- it_behaves_like 'finding a branch'
- end
-
- context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding a branch'
-
- context 'force_reload is true' do
- it 'should reload Rugged::Repository' do
- expect(Rugged::Repository).to receive(:new).twice.and_call_original
-
- repository.find_branch('master')
- branch = repository.find_branch('master', force_reload: true)
+ it 'should handle non-existent branch' do
+ branch = repository.find_branch('this-is-garbage')
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
- end
- end
-
- context 'force_reload is false' do
- it 'should not reload Rugged::Repository' do
- expect(Rugged::Repository).to receive(:new).once.and_call_original
-
- branch = repository.find_branch('master', force_reload: false)
-
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
- end
- end
+ expect(branch).to eq(nil)
end
end
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 10341486512..25827423914 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -12,7 +12,8 @@ describe Gitlab::ImportSources do
'FogBugz' => 'fogbugz',
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
- 'Gitea' => 'gitea'
+ 'Gitea' => 'gitea',
+ 'Manifest file' => 'manifest'
}
expect(described_class.options).to eq(expected)
@@ -31,6 +32,7 @@ describe Gitlab::ImportSources do
git
gitlab_project
gitea
+ manifest
)
expect(described_class.values).to eq(expected)
@@ -63,7 +65,8 @@ describe Gitlab::ImportSources do
'fogbugz' => Gitlab::FogbugzImport::Importer,
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
- 'gitea' => Gitlab::LegacyGithubImport::Importer
+ 'gitea' => Gitlab::LegacyGithubImport::Importer,
+ 'manifest' => nil
}
import_sources.each do |name, klass|
@@ -82,7 +85,8 @@ describe Gitlab::ImportSources do
'fogbugz' => 'FogBugz',
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
- 'gitea' => 'Gitea'
+ 'gitea' => 'Gitea',
+ 'manifest' => 'Manifest file'
}
import_sources.each do |name, title|
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
new file mode 100644
index 00000000000..ab305fb2316
--- /dev/null
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::ManifestImport::Manifest, :postgresql do
+ let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
+ let(:manifest) { described_class.new(file) }
+
+ describe '#valid?' do
+ context 'valid file' do
+ it { expect(manifest.valid?).to be true }
+ end
+
+ context 'missing or invalid attributes' do
+ let(:file) { Tempfile.new('foo') }
+
+ before do
+ content = <<~EOS
+ <manifest>
+ <remote review="invalid-url" />
+ <project name="platform/build"/>
+ </manifest>
+ EOS
+
+ file.write(content)
+ file.rewind
+ end
+
+ it { expect(manifest.valid?).to be false }
+
+ describe 'errors' do
+ before do
+ manifest.valid?
+ end
+
+ it { expect(manifest.errors).to include('Make sure a <remote> tag is present and is valid.') }
+ it { expect(manifest.errors).to include('Make sure every <project> tag has name and path attributes.') }
+ end
+ end
+ end
+
+ describe '#projects' do
+ it { expect(manifest.projects.size).to eq(660) }
+ it { expect(manifest.projects[0][:name]).to eq('platform/build') }
+ it { expect(manifest.projects[0][:path]).to eq('build/make') }
+ it { expect(manifest.projects[0][:url]).to eq('https://android-review.googlesource.com/platform/build') }
+ end
+end
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
new file mode 100644
index 00000000000..1d01d437535
--- /dev/null
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::ManifestImport::ProjectCreator, :postgresql do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:repository) do
+ {
+ path: 'device/common',
+ url: 'https://android-review.googlesource.com/device/common'
+ }
+ end
+
+ before do
+ group.add_owner(user)
+ end
+
+ subject { described_class.new(repository, group, user) }
+
+ describe '#execute' do
+ it { expect(subject.execute).to be_a(Project) }
+ it { expect { subject.execute }.to change { Project.count }.by(1) }
+ it { expect { subject.execute }.to change { Group.count }.by(1) }
+
+ it 'creates project with valid full path and import url' do
+ subject.execute
+
+ project = Project.last
+
+ expect(project.full_path).to eq(File.join(group.path, 'device/common'))
+ expect(project.import_url).to eq('https://android-review.googlesource.com/device/common')
+ end
+ end
+end
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index d7c5f26ab67..77c475b9f52 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -93,4 +93,10 @@ RSpec.describe NotificationSetting do
end
end
end
+
+ context 'email events' do
+ it 'includes EXCLUDED_WATCHER_EVENTS in EMAIL_EVENTS' do
+ expect(described_class::EMAIL_EVENTS).to include(*described_class::EXCLUDED_WATCHER_EVENTS)
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index a3c20b3b3c1..a544940800a 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require "spec_helper"
describe ProjectWiki do
@@ -10,7 +11,6 @@ describe ProjectWiki do
subject { project_wiki }
- it { is_expected.to delegate_method(:empty?).to :pages }
it { is_expected.to delegate_method(:repository_storage).to :project }
it { is_expected.to delegate_method(:hashed_storage?).to :project }
@@ -92,11 +92,19 @@ describe ProjectWiki do
context "when the wiki has pages" do
before do
project_wiki.create_page("index", "This is an awesome new Gollum Wiki")
+ project_wiki.create_page("another-page", "This is another page")
end
describe '#empty?' do
subject { super().empty? }
it { is_expected.to be_falsey }
+
+ # Re-enable this when https://gitlab.com/gitlab-org/gitaly/issues/1204 is fixed
+ xit 'only instantiates a Wiki page once' do
+ expect(WikiPage).to receive(:new).once.and_call_original
+
+ subject
+ end
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 04d00023b8a..02d31098cfd 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1022,24 +1022,6 @@ describe Repository do
end
end
- describe '#find_branch' do
- context 'fresh_repo is true' do
- it 'delegates the call to raw_repository' do
- expect(repository.raw_repository).to receive(:find_branch).with('master', true)
-
- repository.find_branch('master', fresh_repo: true)
- end
- end
-
- context 'fresh_repo is false' do
- it 'delegates the call to raw_repository' do
- expect(repository.raw_repository).to receive(:find_branch).with('master', false)
-
- repository.find_branch('master', fresh_repo: false)
- end
- end
- end
-
describe '#update_branch_with_hooks' do
let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev