From 13c86bb4ad7a858799440002fa3451e3513167a9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 15 Sep 2017 14:10:08 +0100 Subject: Init store for registry --- app/assets/javascripts/registry/components/app.vue | 0 .../registry/components/collapsible_container.vue | 42 ++++++++++++++++++++++ .../registry/components/registry_table.vue | 0 app/assets/javascripts/registry/index.js | 0 app/assets/javascripts/registry/stores/actions.js | 39 ++++++++++++++++++++ app/assets/javascripts/registry/stores/index.js | 37 +++++++++++++++++++ .../javascripts/registry/stores/mutation_types.js | 9 +++++ .../javascripts/registry/stores/mutations.js | 38 ++++++++++++++++++++ .../vue_shared/components/clipboard_button.vue | 40 +++++++++++++++++++++ 9 files changed, 205 insertions(+) create mode 100644 app/assets/javascripts/registry/components/app.vue create mode 100644 app/assets/javascripts/registry/components/collapsible_container.vue create mode 100644 app/assets/javascripts/registry/components/registry_table.vue create mode 100644 app/assets/javascripts/registry/index.js create mode 100644 app/assets/javascripts/registry/stores/actions.js create mode 100644 app/assets/javascripts/registry/stores/index.js create mode 100644 app/assets/javascripts/registry/stores/mutation_types.js create mode 100644 app/assets/javascripts/registry/stores/mutations.js create mode 100644 app/assets/javascripts/vue_shared/components/clipboard_button.vue (limited to 'app') diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue new file mode 100644 index 00000000000..7014562be08 --- /dev/null +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/assets/javascripts/registry/components/registry_table.vue b/app/assets/javascripts/registry/components/registry_table.vue new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js new file mode 100644 index 00000000000..6c0286e2be6 --- /dev/null +++ b/app/assets/javascripts/registry/stores/actions.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import * as types from './mutation_types'; + +Vue.use(VueResource); + +export const fetchRepos = ({ commit, state }) => { + commit(types.TOGGLE_MAIN_LOADING); + + return Vue.http.get(state.endpoint) + .then(res => res.json()) + .then((response) => { + commit(types.TOGGLE_MAIN_LOADING); + commit(types.SET_REPOS_LIST, response); + }); +}; + +export const fetchList = ({ commit }, list) => { + commit(types.TOGGLE_IMAGE_LOADING, list); + + return Vue.http.get(list.path) + .then(res => res.json()) + .then((response) => { + commit(types.TOGGLE_IMAGE_LOADING, list); + commit(types.SET_IMAGES_LIST, list, response); + }); +}; + +export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path) + .then(res => res.json()) + .then(() => { + commit(types.DELETE_REPO, repo); + }); + +export const deleteImage = ({ commit }, image) => Vue.http.delete(image.path) + .then(res => res.json()) + .then(() => { + commit(types.DELETE_IMAGE, image); + }); diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js new file mode 100644 index 00000000000..6cf9df57f08 --- /dev/null +++ b/app/assets/javascripts/registry/stores/index.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export default new Vuex.Store({ + state: { + isLoading: false, + endpoint: '', // initial endpoint to fetch the repos list + /** + * Each object in `repos` has the following strucure: + * { + * name: String, + * isLoading: Boolean, + * tagsPath: String // endpoint to request the list + * destroyPath: String // endpoit to delete the repo + * list: Array // List of the registry images + * } + * + * Each registry image inside `list` has the following structure: + * { + * tag: String, + * revision: String + * shortRevision: String + * size: Number + * layers: Number + * createdAt: String + * destroyPath: String // endpoit to delete each image + * } + */ + repos: [], + actions, + mutations, + }, +}); diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js new file mode 100644 index 00000000000..fb4e24e10e3 --- /dev/null +++ b/app/assets/javascripts/registry/stores/mutation_types.js @@ -0,0 +1,9 @@ +export const FETCH_REPOS_LIST = 'FETCH_REPOS_LIST'; +export const DELETE_REPO = 'DELETE_REPO'; +export const SET_REPOS_LIST = 'SET_REPOS_LIST'; +export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING'; + +export const FETCH_IMAGES_LIST = 'FETCH_IMAGES_LIST'; +export const SET_IMAGES_LIST = 'SET_IMAGES_LIST'; +export const DELETE_IMAGE = 'DELETE_IMAGE'; +export const TOGGLE_IMAGE_LOADING = 'TOGGLE_MAIN_LOADING'; diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js new file mode 100644 index 00000000000..5fa41fb5255 --- /dev/null +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -0,0 +1,38 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_REPOS_LIST](state, list) { + Object.assign(state, { + repos: list.map(el => ({ + name: el.name, + isLoading: false, + canDelete: !!el.destroy_path, + destroyPath: el.destroy_path, + list: [], + })), + }); + }, + + [types.TOGGLE_MAIN_LOADING](state) { + Object.assign(state, { isLoading: !state.isLoading }); + }, + + [types.SET_IMAGES_LIST](state, image, list) { + const listToUpdate = state.repos.find(el => el.name === image.name); + listToUpdate.list = list.map(element => ({ + tag: element.name, + revision: element.revision, + shortRevision: element.short_revision, + size: element.size, + layers: element.layers, + createdAt: element.created_at, + destroyPath: element.destroy_path, + canDelete: !!element.destroy_path, + })); + }, + + [types.TOGGLE_IMAGE_LOADING](state, image) { + const listToUpdate = state.repos.find(el => el.name === image.name); + listToUpdate.isLoading = !listToUpdate.isLoading; + }, +}; diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue new file mode 100644 index 00000000000..ebb3dbd0112 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -0,0 +1,40 @@ + +import Clipboard from 'vendor/clipboard'; + + + + -- cgit v1.2.3 From 23024a70dbc50dbd0114ed715c906cac1a9b1d59 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 15 Sep 2017 17:05:46 +0100 Subject: Adds clipboard button component --- .../registry/components/collapsible_container.vue | 117 ++++++++++++++++++--- .../registry/components/registry_table.vue | 0 .../vue_shared/components/clipboard_button.vue | 5 +- 3 files changed, 105 insertions(+), 17 deletions(-) delete mode 100644 app/assets/javascripts/registry/components/registry_table.vue (limited to 'app') diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 7014562be08..20ebedd2b45 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,18 +1,24 @@ diff --git a/app/assets/javascripts/registry/components/registry_table.vue b/app/assets/javascripts/registry/components/registry_table.vue deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index ebb3dbd0112..cb9ad4c7dee 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -1,7 +1,6 @@ - -import Clipboard from 'vendor/clipboard'; - + diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 20ebedd2b45..6be2aa60ebd 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,24 +1,22 @@ diff --git a/app/assets/javascripts/registry/constants.js b/app/assets/javascripts/registry/constants.js new file mode 100644 index 00000000000..d3de6441dae --- /dev/null +++ b/app/assets/javascripts/registry/constants.js @@ -0,0 +1,13 @@ +export const errorMessagesTypes = { + FETCH_REGISTRY: 'FETCH_REGISTRY', + FETCH_REPOS: 'FETCH_REPOS', + DELETE_REPO: 'DELETE_REPO', + DELETE_REGISTRY: 'DELETE_REGISTRY', +}; + +export const errorMessages = { + [errorMessagesTypes.FETCH_REGISTRY]: 'Something went wrong while fetching the registry list.', + [errorMessagesTypes.FETCH_REPOS]: 'Something went wrong while fetching the repositories.', + [errorMessagesTypes.DELETE_REPO]: 'Something went wrong while deleting the repository.', + [errorMessagesTypes.DELETE_REGISTRY]: 'Something went wrong while deleting registry.', +}; diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js index e69de29bb2d..4f7895897b2 100644 --- a/app/assets/javascripts/registry/index.js +++ b/app/assets/javascripts/registry/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import Translate from '../vue_shared/translate'; +import registryApp from './components/app.vue'; + +// Vue.use(Translate); + +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#js-vue-registry-images', + components: { + registryApp, + }, + data() { + const dataset = document.querySelector(this.$options.el).dataset; + return { + endpoint: dataset.endpoint, + }; + }, + render(createElement) { + return createElement('registry-app', { + props: { + endpoint: this.endpoint, + }, + }); + }, +})); diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index 6c0286e2be6..5dda16b8d9a 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -16,13 +16,13 @@ export const fetchRepos = ({ commit, state }) => { }; export const fetchList = ({ commit }, list) => { - commit(types.TOGGLE_IMAGE_LOADING, list); + commit(types.TOGGLE_REGISTRY_LIST_LOADING, list); return Vue.http.get(list.path) .then(res => res.json()) .then((response) => { - commit(types.TOGGLE_IMAGE_LOADING, list); - commit(types.SET_IMAGES_LIST, list, response); + commit(types.TOGGLE_REGISTRY_LIST_LOADING, list); + commit(types.SET_REGISTRY_LIST, list, response); }); }; @@ -32,8 +32,11 @@ export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.path) commit(types.DELETE_REPO, repo); }); -export const deleteImage = ({ commit }, image) => Vue.http.delete(image.path) +export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.path) .then(res => res.json()) .then(() => { commit(types.DELETE_IMAGE, image); }); + +export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); +export const toggleIsLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); diff --git a/app/assets/javascripts/registry/stores/getters.js b/app/assets/javascripts/registry/stores/getters.js new file mode 100644 index 00000000000..6c6ed0cd738 --- /dev/null +++ b/app/assets/javascripts/registry/stores/getters.js @@ -0,0 +1,2 @@ +export const isLoading = state => state.isLoading; +export const repos = state => state.repos; \ No newline at end of file diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js index 6cf9df57f08..78b67881210 100644 --- a/app/assets/javascripts/registry/stores/index.js +++ b/app/assets/javascripts/registry/stores/index.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import actions from './actions'; +import * as actions from './actions'; +import * as getters from './getters'; import mutations from './mutations'; Vue.use(Vuex); @@ -31,7 +32,8 @@ export default new Vuex.Store({ * } */ repos: [], - actions, - mutations, }, + actions, + getters, + mutations, }); diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js index fb4e24e10e3..aece401a24a 100644 --- a/app/assets/javascripts/registry/stores/mutation_types.js +++ b/app/assets/javascripts/registry/stores/mutation_types.js @@ -1,9 +1,10 @@ +export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT'; export const FETCH_REPOS_LIST = 'FETCH_REPOS_LIST'; export const DELETE_REPO = 'DELETE_REPO'; export const SET_REPOS_LIST = 'SET_REPOS_LIST'; export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING'; export const FETCH_IMAGES_LIST = 'FETCH_IMAGES_LIST'; -export const SET_IMAGES_LIST = 'SET_IMAGES_LIST'; +export const SET_REGISTRY_LIST = 'SET_REGISTRY_LIST'; export const DELETE_IMAGE = 'DELETE_IMAGE'; -export const TOGGLE_IMAGE_LOADING = 'TOGGLE_MAIN_LOADING'; +export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING'; diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js index 5fa41fb5255..796548bffec 100644 --- a/app/assets/javascripts/registry/stores/mutations.js +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -1,14 +1,22 @@ import * as types from './mutation_types'; export default { + + [types.SET_MAIN_ENDPOINT](state, endpoint) { + Object.assign(state, { endpoint }); + }, + [types.SET_REPOS_LIST](state, list) { Object.assign(state, { repos: list.map(el => ({ - name: el.name, - isLoading: false, canDelete: !!el.destroy_path, destroyPath: el.destroy_path, + isLoading: false, list: [], + location: el.location, + name: el.name, + tagsPath: el.tags_path, + id: el.id, })), }); }, @@ -17,8 +25,29 @@ export default { Object.assign(state, { isLoading: !state.isLoading }); }, - [types.SET_IMAGES_LIST](state, image, list) { - const listToUpdate = state.repos.find(el => el.name === image.name); + [types.SET_REGISTRY_LIST](state, repo, list) { + // mock + list = [ + { + name: 'centos6', + short_revision: '0b6091a66', + revision: '0b6091a665af68bbbbb36a3e088ec3cd6f35389deebf6d4617042d56722d76fb', + size: 706, + layers: 19, + created_at: 1505828744434, + }, + { + name: 'centos7', + short_revision: 'b118ab5b0', + revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', + size: 679, + layers: 19, + created_at: 1505828744434, + }, + ]; + + const listToUpdate = state.repos.find(el => el.id === repo.id); + listToUpdate.list = list.map(element => ({ tag: element.name, revision: element.revision, @@ -31,8 +60,8 @@ export default { })); }, - [types.TOGGLE_IMAGE_LOADING](state, image) { - const listToUpdate = state.repos.find(el => el.name === image.name); + [types.TOGGLE_REGISTRY_LIST_LOADING](state, list) { + const listToUpdate = state.repos.find(el => el.id === list.id); listToUpdate.isLoading = !listToUpdate.isLoading; }, }; diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index cb9ad4c7dee..fbf7233b13d 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -14,11 +14,11 @@ }, }, mounted() { - return new Clipboard(this.$refs.btn, { - text: () => { - return this.text; - }, - }); + // return new Clipboard(this.$refs.btn, { + // text: () => { + // return this.text; + // }, + // }); } }; diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss index 3266714396e..089a693efe4 100644 --- a/app/assets/stylesheets/pages/container_registry.scss +++ b/app/assets/stylesheets/pages/container_registry.scss @@ -9,6 +9,10 @@ .container-image-head { padding: 0 16px; line-height: 4em; + + &:hover { + text-decoration: underline; + } } .table.tags { diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 71e7dc70a4d..89093e4172a 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -6,6 +6,37 @@ module Projects def index @images = project.container_repositories + + respond_to do |format| + format.html + format.json do + # render json: @images + render json: [ + { + name: 'gitlab-org/omnibus-gitlab/foo', + tags_path: 'foo', + destroy_path: 'bar', + location: 'foo', + id: '134', + destroy_path: 'bar' + }, + { + name: 'gitlab-org/omnibus-gitlab', + tags_path: 'foo', + destroy_path: 'bar', + location: 'foo', + id: '123', + }, + { + name: 'gitlab-org/omnibus-gitlab/bar', + tags_path: 'foo', + destroy_path: 'bar', + location: 'foo', + id: '973', + } + ] + end + end end def destroy diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 5661af01302..ab263091c1f 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -52,9 +52,15 @@ #{escape_once(@project.container_registry_url)}/optional-image-name:tag #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag - - if @images.blank? - %p.settings-message.text-center.append-bottom-default - No container images stored for this project. Add one by following the - instructions above. - - else - = render partial: 'image', collection: @images + #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json)}} + + = page_specific_javascript_bundle_tag('common_vue') + = page_specific_javascript_bundle_tag('registry_list') + + + -# - if @images.blank? + -# %p.settings-message.text-center.append-bottom-default + -# No container images stored for this project. Add one by following the + -# instructions above. + -# - else + -# = render partial: 'image', collection: @images -- cgit v1.2.3 From 6c63520ef5735e56749c77b495f8137a20942504 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 19 Sep 2017 17:53:57 +0100 Subject: Removes 2 column layout. Adds i18n support --- .../projects/registry/repositories/index.html.haml | 102 ++++++++++----------- 1 file changed, 47 insertions(+), 55 deletions(-) (limited to 'app') diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index ab263091c1f..4a76431494c 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -1,66 +1,58 @@ - page_title "Container Registry" -.row.prepend-top-default.append-bottom-default - .col-lg-3 - %h4.prepend-top-0 +%section + .settings-header + %h4 = page_title %p - With the Docker Container Registry integrated into GitLab, every project - can have its own space to store its Docker images. + = _('With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images.') %p.append-bottom-0 = succeed '.' do Learn more about - = link_to 'Container Registry', help_page_path('user/project/container_registry'), target: '_blank' + = link_to _('Container Registry'), help_page_path('user/project/container_registry'), target: '_blank' + .row + .col-lg-12 + .panel.panel-default + .panel-heading + %h4.panel-title + = _('How to use the Container Registry') + .panel-body + %p + = _('First log in to GitLab’s Container Registry using your GitLab username and password. If you have') + = link_to _('2FA enabled'), help_page_path('user/profile/account/two_factor_authentication'), target: '_blank' + you need to use a + = succeed ':' do + = link_to _('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank' + %pre + docker login #{Gitlab.config.registry.host_port} + %br + %p + = _("Once you log in, you’re free to create and upload a container image using the common") + %code + = _('build') + = _('and') + %code push + = _('commands:') + %pre + :plain + docker build -t #{escape_once(@project.container_registry_url)} . + docker push #{escape_once(@project.container_registry_url)} - .col-lg-9 - .panel.panel-default - .panel-heading - %h4.panel-title - How to use the Container Registry - .panel-body - %p - First log in to GitLab’s Container Registry using your GitLab username - and password. If you have - = link_to '2FA enabled', help_page_path('user/profile/account/two_factor_authentication'), target: '_blank' - you need to use a - = succeed ':' do - = link_to 'personal access token', help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank' - %pre - docker login #{Gitlab.config.registry.host_port} - %br - %p - Once you log in, you’re free to create and upload a container image - using the common - %code build - and - %code push - commands: - %pre - :plain - docker build -t #{escape_once(@project.container_registry_url)} . - docker push #{escape_once(@project.container_registry_url)} + %hr + %h5.prepend-top-default + = _('Use different image names') + %p.light + = _('GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:') + %pre + :plain + #{escape_once(@project.container_registry_url)}:tag + #{escape_once(@project.container_registry_url)}/optional-image-name:tag + #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag - %hr - %h5.prepend-top-default - Use different image names - %p.light - GitLab supports up to 3 levels of image names. The following - examples of images are valid for your project: - %pre - :plain - #{escape_once(@project.container_registry_url)}:tag - #{escape_once(@project.container_registry_url)}/optional-image-name:tag - #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag - #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json)}} + .row + .col-lg-12 + #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json)}} - = page_specific_javascript_bundle_tag('common_vue') - = page_specific_javascript_bundle_tag('registry_list') - - - -# - if @images.blank? - -# %p.settings-message.text-center.append-bottom-default - -# No container images stored for this project. Add one by following the - -# instructions above. - -# - else - -# = render partial: 'image', collection: @images + = page_specific_javascript_bundle_tag('common_vue') + = page_specific_javascript_bundle_tag('registry_list') -- cgit v1.2.3 From 7af585f1667935165701ec290e6ce37a436ad4c6 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 13 Sep 2017 17:11:10 +0300 Subject: Fast forward merge: basic implemenation[ci skip] --- app/controllers/projects_controller.rb | 1 + app/models/project.rb | 17 +++++++++++++++ app/models/repository.rb | 19 +++++++++++++++++ app/serializers/merge_request_entity.rb | 5 +++++ app/services/merge_requests/ff_merge_service.rb | 24 ++++++++++++++++++++++ app/services/merge_requests/merge_service.rb | 5 +++++ .../_merge_request_merge_settings.html.haml | 22 ++++++++++++++++++++ 7 files changed, 93 insertions(+) create mode 100644 app/services/merge_requests/ff_merge_service.rb (limited to 'app') diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b13034d3333..a738ca9f361 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController :tag_list, :visibility_level, :template_name, + :merge_method, project_feature_attributes: %i[ builds_access_level diff --git a/app/models/project.rb b/app/models/project.rb index f7221e4f3b2..cd1fa734e78 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1557,6 +1557,23 @@ class Project < ActiveRecord::Base persisted? && path_changed? end + def merge_method + if self.merge_requests_ff_only_enabled + :ff + else + :merge + end + end + + def merge_method=(method) + self.merge_requests_ff_only_enabled = method.to_s == "ff" + end + + def ff_merge_must_be_possible? + self.merge_requests_ff_only_enabled + end + # alias_method :merge_requests_ff_only_enabled?, :merge_requests_ff_only_enabled + private def storage diff --git a/app/models/repository.rb b/app/models/repository.rb index f11cf1b065d..2df6c9c80da 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -854,6 +854,25 @@ class Repository end end + def ff_merge(user, source, target_branch, merge_request: nil) + our_commit = rugged.branches[target_branch].target + their_commit = + if source.is_a?(Gitlab::Git::Commit) + source.raw_commit + else + rugged.lookup(source) + end + + raise 'Invalid merge target' if our_commit.nil? + raise 'Invalid merge source' if their_commit.nil? + + with_branch(user, target_branch) do |start_commit| + merge_request&.update(in_progress_merge_commit_sha: their_commit.oid) + + their_commit.oid + end + end + def revert( user, commit, branch_name, message, start_branch_name: nil, start_project: project) diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 07650ce6f20..1f5ce2ef58c 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -13,6 +13,11 @@ class MergeRequestEntity < IssuableEntity expose :target_branch expose :target_project_id + expose :ff_only_enabled do |merge_request| + merge_request.project.merge_requests_ff_only_enabled + end + + # Events expose :merge_event, using: EventEntity expose :closed_event, using: EventEntity diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb new file mode 100644 index 00000000000..ba6853b835a --- /dev/null +++ b/app/services/merge_requests/ff_merge_service.rb @@ -0,0 +1,24 @@ +module MergeRequests + # MergeService class + # + # Do git fast-forward merge and in case of success + # mark merge request as merged and execute all hooks and notifications + # Executed when you do fast-forward merge via GitLab UI + # + class FfMergeService < MergeRequests::MergeService + private + + def commit + repository.ff_merge(current_user, + source, + merge_request.target_branch, + merge_request: merge_request) + rescue Gitlab::Git::HooksService::PreReceiveError => e + raise MergeError, e.message + rescue StandardError => e + raise MergeError, "Something went wrong during merge: #{e.message}" + ensure + merge_request.update(in_progress_merge_commit_sha: nil) + end + end +end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 07cbd8f92a9..0d75c68feb2 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -11,6 +11,11 @@ module MergeRequests attr_reader :merge_request, :source def execute(merge_request) + if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService) + FfMergeService.new(project, current_user, params).execute(merge_request) + return + end + @merge_request = merge_request unless @merge_request.mergeable? diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index 1dd8778f800..0016526f788 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -1,5 +1,27 @@ - form = local_assigns.fetch(:form) +.form-group + = label_tag :merge_method_merge, class: 'label-light' do + Merge method + .radio + = label_tag :project_merge_method_merge do + = form.radio_button :merge_method, :merge, class: "js-merge-method-radio" + %strong Merge commit + %br + %span.descr + A merge commit is created for every merge, and merging is allowed as long as there are no conflicts. + + .radio + = label_tag :project_merge_method_ff do + = form.radio_button :merge_method, :ff, class: "js-merge-method-radio" + %strong Fast-forward merge + %br + %span.descr + No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. + %br + %span.descr + When fast-forward merge is not possible, the user must first rebase locally. + .form-group .checkbox.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) } = form.label :only_allow_merge_if_pipeline_succeeds do -- cgit v1.2.3 From dd35c1d556ea5809ef2aefd8ec5741d7b4d83372 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 14 Sep 2017 17:26:03 +0300 Subject: Add spec for FfMergeService --- app/models/project.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'app') diff --git a/app/models/project.rb b/app/models/project.rb index cd1fa734e78..d456d983243 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1572,7 +1572,6 @@ class Project < ActiveRecord::Base def ff_merge_must_be_possible? self.merge_requests_ff_only_enabled end - # alias_method :merge_requests_ff_only_enabled?, :merge_requests_ff_only_enabled private -- cgit v1.2.3 From ccdf687baa82dd0e0f809cfb24dc892db756bad7 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 15 Sep 2017 16:23:03 -0500 Subject: Port fast-forward merge request setting from EE --- .../_merge_request_fast_forward_settings.html.haml | 13 +++++++++++++ .../_merge_request_merge_settings.html.haml | 22 ---------------------- .../projects/_merge_request_settings.html.haml | 13 +++++++++++++ 3 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 app/views/projects/_merge_request_fast_forward_settings.html.haml (limited to 'app') diff --git a/app/views/projects/_merge_request_fast_forward_settings.html.haml b/app/views/projects/_merge_request_fast_forward_settings.html.haml new file mode 100644 index 00000000000..9d357293a2f --- /dev/null +++ b/app/views/projects/_merge_request_fast_forward_settings.html.haml @@ -0,0 +1,13 @@ +- form = local_assigns.fetch(:form) +- project = local_assigns.fetch(:project) + +.radio + = label_tag :project_merge_method_ff do + = form.radio_button :merge_method, :ff, class: "js-merge-method-radio" + %strong Fast-forward merge + %br + %span.descr + No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. + %br + %span.descr + When fast-forward merge is not possible, the user must first rebase locally. diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index 0016526f788..1dd8778f800 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -1,27 +1,5 @@ - form = local_assigns.fetch(:form) -.form-group - = label_tag :merge_method_merge, class: 'label-light' do - Merge method - .radio - = label_tag :project_merge_method_merge do - = form.radio_button :merge_method, :merge, class: "js-merge-method-radio" - %strong Merge commit - %br - %span.descr - A merge commit is created for every merge, and merging is allowed as long as there are no conflicts. - - .radio - = label_tag :project_merge_method_ff do - = form.radio_button :merge_method, :ff, class: "js-merge-method-radio" - %strong Fast-forward merge - %br - %span.descr - No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. - %br - %span.descr - When fast-forward merge is not possible, the user must first rebase locally. - .form-group .checkbox.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) } = form.label :only_allow_merge_if_pipeline_succeeds do diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index cc5afa943cf..939750ed1e5 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -1,3 +1,16 @@ - form = local_assigns.fetch(:form) +.form-group + = label_tag :merge_method_merge, class: 'label-light' do + Merge method + .radio + = label_tag :project_merge_method_merge do + = form.radio_button :merge_method, :merge, class: "js-merge-method-radio" + %strong Merge commit + %br + %span.descr + A merge commit is created for every merge, and merging is allowed as long as there are no conflicts. + + = render 'merge_request_fast_forward_settings', project: @project, form: form + = render 'projects/merge_request_merge_settings', form: form -- cgit v1.2.3 From f7f9847b26fd9f401039908843de2b660473e73d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 18 Sep 2017 17:06:47 -0500 Subject: Port fast-forward MR widget states from EE See https://gitlab.com/gitlab-org/gitlab-ce/issues/20076 --- .../components/mr_widget_status_icon.js | 1 + .../components/states/mr_widget_conflicts.js | 51 ++++++++++++++-------- .../components/states/mr_widget_ready_to_merge.js | 7 +++ .../stores/mr_widget_store.js | 1 + app/serializers/merge_request_entity.rb | 1 - 5 files changed, 41 insertions(+), 20 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js index 703f3a56a34..3cbcbdd52c5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js @@ -26,6 +26,7 @@ export default {