Skip to content
Snippets Groups Projects
Commit 0640b3d1 authored by Oswaldo Ferreir's avatar Oswaldo Ferreir
Browse files

Store MergeWorker JID on merge request, and clean up stuck merges

parent b12107a0
No related branches found
No related tags found
No related merge requests found
Showing
with 104 additions and 41 deletions
Loading
Loading
@@ -3,4 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
import statusIcon from '../mr_widget_status_icon';
 
export default {
name: 'MRWidgetLocked',
name: 'MRWidgetMerging',
props: {
mr: { type: Object, required: true },
},
Loading
Loading
@@ -13,7 +13,7 @@ export default {
<status-icon status="loading" />
<div class="media-body">
<h4>
This merge request is in the process of being merged, during which time it is locked and cannot be closed
This merge request is in the process of being merged
</h4>
<section class="mr-info-list">
<p>
Loading
Loading
Loading
Loading
@@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
export { default as ClosedState } from './components/states/mr_widget_closed';
export { default as LockedState } from './components/states/mr_widget_locked';
export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived';
export { default as ConflictsState } from './components/states/mr_widget_conflicts';
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ import {
WidgetRelatedLinks,
MergedState,
ClosedState,
LockedState,
MergingState,
WipState,
ArchivedState,
ConflictsState,
Loading
Loading
@@ -212,7 +212,7 @@ export default {
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
'mr-widget-locked': LockedState,
'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WipState,
'mr-widget-archived': ArchivedState,
Loading
Loading
Loading
Loading
@@ -73,6 +73,7 @@ export default class MergeRequestStore {
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.mergeOngoing = data.merge_ongoing;
 
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
Loading
Loading
@@ -94,6 +95,11 @@ export default class MergeRequestStore {
}
 
setState(data) {
if (this.mergeOngoing) {
this.state = 'merging';
return;
}
if (this.isOpen) {
this.state = getStateKey.call(this, data);
} else {
Loading
Loading
@@ -104,9 +110,6 @@ export default class MergeRequestStore {
case 'closed':
this.state = 'closed';
break;
case 'locked':
this.state = 'locked';
break;
default:
this.state = null;
}
Loading
Loading
const stateToComponentMap = {
merged: 'mr-widget-merged',
closed: 'mr-widget-closed',
locked: 'mr-widget-locked',
merging: 'mr-widget-merging',
conflicts: 'mr-widget-conflicts',
missingBranch: 'mr-widget-missing-branch',
workInProgress: 'mr-widget-wip',
Loading
Loading
@@ -20,7 +20,7 @@ const stateToComponentMap = {
};
 
const statesToShowHelpWidget = [
'locked',
'merging',
'conflicts',
'workInProgress',
'readyToMerge',
Loading
Loading
Loading
Loading
@@ -67,11 +67,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
 
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
labels
 
set_pipeline_variables
Loading
Loading
Loading
Loading
@@ -61,16 +61,6 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened
end
 
after_transition any => :locked do |merge_request, transition|
merge_request.locked_at = Time.now
merge_request.save
end
after_transition locked: (any - :locked) do |merge_request, transition|
merge_request.locked_at = nil
merge_request.save
end
state :opened
state :closed
state :merged
Loading
Loading
@@ -392,6 +382,12 @@ class MergeRequest < ActiveRecord::Base
'Source project is not a fork of the target project'
end
 
def merge_ongoing?
return false unless merge_jid
Gitlab::SidekiqStatus.num_running([merge_jid]) > 0
end
def closed_without_fork?
closed? && source_project_missing?
end
Loading
Loading
@@ -725,12 +721,6 @@ class MergeRequest < ActiveRecord::Base
end
end
 
def locked_long_ago?
return false unless locked?
locked_at.nil? || locked_at < (Time.now - 1.day)
end
def has_ci?
has_ci_integration = source_project.try(:ci_service)
uses_gitlab_ci = all_pipelines.any?
Loading
Loading
Loading
Loading
@@ -2,7 +2,6 @@ class MergeRequestEntity < IssuableEntity
include RequestAwareEntity
 
expose :in_progress_merge_commit_sha
expose :locked_at
expose :merge_commit_sha
expose :merge_error
expose :merge_params
Loading
Loading
@@ -32,6 +31,7 @@ class MergeRequestEntity < IssuableEntity
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
 
# Booleans
expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
expose :source_branch_exists?, as: :source_branch_exists
expose :mergeable_discussions_state?, as: :mergeable_discussions_state
Loading
Loading
Loading
Loading
@@ -7,6 +7,8 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
 
merge_request.update_column(:merge_jid, jid)
MergeRequests::MergeService.new(merge_request.target_project, current_user, params)
.execute(merge_request)
end
Loading
Loading
class StuckMergeJobsWorker
include Sidekiq::Worker
include CronjobQueue
def perform
stuck_merge_requests.find_in_batches(batch_size: 100) do |group|
jids = group.map(&:merge_jid)
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids)
if completed_jids.any?
completed_ids = group.select { |merge_request| completed_jids.include?(merge_request.merge_jid) }.map(&:id)
apply_current_state!(completed_jids, completed_ids)
end
end
end
private
def apply_current_state!(completed_jids, completed_ids)
merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
merge_requests.where(merge_commit_sha: nil).update_all(state: :opened)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
def stuck_merge_requests
MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil)
end
end
---
title: Unlock stuck merge request and set the proper state
merge_request: 13207
author:
Loading
Loading
@@ -395,6 +395,10 @@ Settings.cron_jobs['remove_old_web_hook_logs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_old_web_hook_logs_worker']['cron'] ||= '40 0 * * *'
Settings.cron_jobs['remove_old_web_hook_logs_worker']['job_class'] = 'RemoveOldWebHookLogsWorker'
 
Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *'
Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker'
#
# GitLab Shell
#
Loading
Loading
class AddMergeJidToMergeRequests < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :merge_requests, :merge_jid, :string
end
end
class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration
DOWNTIME = false
def up
remove_column :merge_requests, :locked_at
end
def down
add_column :merge_requests, :locked_at, :datetime_with_timezone
end
end
Loading
Loading
@@ -840,7 +840,6 @@ ActiveRecord::Schema.define(version: 20170803130232) do
t.integer "target_project_id", null: false
t.integer "iid"
t.text "description"
t.datetime "locked_at"
t.integer "updated_by_id"
t.text "merge_error"
t.text "merge_params"
Loading
Loading
@@ -858,6 +857,7 @@ ActiveRecord::Schema.define(version: 20170803130232) do
t.integer "last_edited_by_id"
t.integer "head_pipeline_id"
t.boolean "ref_fetched"
t.string "merge_jid"
end
 
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
Loading
Loading
Loading
Loading
@@ -438,7 +438,6 @@ X-Gitlab-Event: Note Hook
"iid": 1,
"description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
"position": 0,
"locked_at": null,
"source":{
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
Loading
Loading
Loading
Loading
@@ -219,4 +219,17 @@ describe 'Merge request', :js do
expect(page).to have_field('remove-source-branch-input', disabled: true)
end
end
context 'ongoing merge process' do
it 'shows Merging state' do
allow_any_instance_of(MergeRequest).to receive(:merge_ongoing?).and_return(true)
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).not_to have_button('Merge')
expect(page).to have_content('This merge request is in the process of being merged')
end
end
end
Loading
Loading
@@ -19,7 +19,6 @@
"human_time_estimate": { "type": ["integer", "null"] },
"human_total_time_spent": { "type": ["integer", "null"] },
"in_progress_merge_commit_sha": { "type": ["string", "null"] },
"locked_at": { "type": ["string", "null"] },
"merge_error": { "type": ["string", "null"] },
"merge_commit_sha": { "type": ["string", "null"] },
"merge_params": { "type": ["object", "null"] },
Loading
Loading
@@ -94,7 +93,8 @@
"commit_change_content_path": { "type": "string" },
"remove_wip_path": { "type": "string" },
"commits_count": { "type": "integer" },
"remove_source_branch": { "type": ["boolean", "null"] }
"remove_source_branch": { "type": ["boolean", "null"] },
"merge_ongoing": { "type": "boolean" }
},
"additionalProperties": false
}
import Vue from 'vue';
import lockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_locked';
import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging';
 
describe('MRWidgetLocked', () => {
describe('MRWidgetMerging', () => {
describe('props', () => {
it('should have props', () => {
const { mr } = lockedComponent.props;
const { mr } = mergingComponent.props;
 
expect(mr.type instanceof Object).toBeTruthy();
expect(mr.required).toBeTruthy();
Loading
Loading
@@ -13,7 +13,7 @@ describe('MRWidgetLocked', () => {
 
describe('template', () => {
it('should have correct elements', () => {
const Component = Vue.extend(lockedComponent);
const Component = Vue.extend(mergingComponent);
const mr = {
targetBranchPath: '/branch-path',
targetBranch: 'branch',
Loading
Loading
@@ -24,7 +24,7 @@ describe('MRWidgetLocked', () => {
}).$el;
 
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
expect(el.innerText).toContain('it is locked');
expect(el.innerText).toContain('This merge request is in the process of being merged');
expect(el.innerText).toContain('changes will be merged into');
expect(el.querySelector('.label-branch a').getAttribute('href')).toEqual(mr.targetBranchPath);
expect(el.querySelector('.label-branch a').textContent).toContain(mr.targetBranch);
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment