-
-
Save MVKozlov/835b883c558bc724dbe11a486e67b8da to your computer and use it in GitHub Desktop.
simple (and dirty) sync between redmine issues and gitlab issues
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'faraday' | |
require 'json' | |
require 'gitlab' | |
module Redmine | |
Host = nil | |
APIKey = nil | |
def self.connection | |
raise 'must define a Host' if Host.nil? | |
@connection ||= Faraday.new(:url => Host) do |faraday| | |
# faraday.response :logger | |
faraday.adapter Faraday.default_adapter | |
end | |
end | |
def self.get(path, attrs = {}) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.get(path, attrs) do |req| | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
JSON.parse result.body | |
end | |
def self.post(path, attrs = {}, body = nil) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.post(path, attrs) do |req| | |
req.body = body | |
req.headers['Content-Type'] = 'application/json' | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
JSON.parse result.body | |
end | |
def self.put(path, attrs = {}, body = nil) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.put(path, attrs) do |req| | |
req.body = body | |
req.headers['Content-Type'] = 'application/json' | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
end | |
class Base | |
attr_accessor :id, :attributes | |
def self.pluralized_resource_name | |
@pluralized_resource_name ||= "#{self.resource_name}s" | |
end | |
def self.resource_name | |
@resource_name ||= self.name.split('::').last.downcase | |
end | |
def self.list(options = {}) | |
list = Redmine.get "#{pluralized_resource_name}.json", options | |
raise "did not find any #{pluralized_resource_name} in #{list.inspect}" if list[pluralized_resource_name].nil? | |
list[pluralized_resource_name].collect do |attributes| | |
obj = new | |
obj.attributes = attributes | |
obj | |
end | |
end | |
def self.find(id) | |
@find ||= {} | |
return @find[id] if @find[id] | |
response = Redmine.get "#{pluralized_resource_name}/#{id}.json" | |
obj = new | |
obj.attributes = response[resource_name] | |
@find[id] = obj | |
end | |
def method_missing(sym) | |
self.attributes[sym.to_s] | |
end | |
def id | |
self.attributes['id'] | |
end | |
end | |
class Project < Base | |
def issues(options = {}) | |
@issues ||= Issue.list(options.merge(:status_id => '*', :project_id => self.id, :limit => 999)) | |
end | |
def categories | |
@categories ||= IssueCategory.list :project_id => self.id | |
end | |
def category_by_name(name) | |
@category_by_name ||= {} | |
@category_by_name[name] ||= categories.detect { |category| category.name == name } | |
end | |
def self.by_identifier(identifier) | |
self.list.detect { |project| project.identifier == identifier } | |
end | |
end | |
class User < Base | |
def self.by_email(email) | |
@by_email ||= {} | |
@by_email[email] ||= self.list.detect { |user| user.mail == email } | |
end | |
end | |
class Issue < Base | |
def self.create(project, subject, description, attributes = {}) | |
body = { | |
:issue => { | |
:project_id => project.id, | |
:subject => subject, | |
:description => description, | |
:tracker_id => Tracker.first.id, | |
:priority_id => 4 | |
}.merge(attributes) | |
}.to_json | |
response = Redmine.post 'issues.json', {}, body | |
end | |
def update(new_attributes = {}) | |
changes = {} | |
new_attributes.each do |key, value| | |
if key.match(/_id$/) | |
if self.attributes[key.to_s.gsub(/_id$/, '')] and self.attributes[key.to_s.gsub(/_id$/, '')]['id'].to_s != value.to_s | |
changes[key] = value | |
end | |
else | |
changes[key] = value if self.attributes[key.to_s].to_s != value.to_s | |
end | |
end | |
if changes.empty? | |
puts 'no changes !' | |
return | |
end | |
puts "changes: #{changes.inspect}" | |
response = Redmine.put "issues/#{self.id}.json", {}, { :issue => changes }.to_json | |
end | |
def author | |
Redmine::User.find self.attributes['author']['id'] | |
end | |
def assignee | |
Redmine::User.find self.attributes['assigned_to']['id'] rescue nil | |
end | |
end | |
class IssueStatus < Base | |
def self.pluralized_resource_name ; 'issue_statuses' ; end | |
def self.resource_name ; 'issue_status' ; end | |
def self.by_name(name) | |
@by_name ||= {} | |
@by_name[name] ||= list.detect { |status| status.name == name } | |
end | |
end | |
class IssueCategory < Base | |
def self.pluralized_resource_name ; 'issue_categories' ; end | |
def self.resource_name ; 'issue_category' ; end | |
def self.list(options = {}) | |
raise "must provide a project_id" if options[:project_id].nil? | |
list = Redmine.get "projects/#{options.delete :project_id}/issue_categories.json", options | |
raise "did not find any issue_categories in #{list.inspect}" if list['issue_categories'].nil? | |
list['issue_categories'].collect do |attributes| | |
obj = new | |
obj.attributes = attributes | |
obj | |
end | |
end | |
end | |
class Tracker < Base | |
def self.first | |
@first ||= self.list.first | |
end | |
end | |
end | |
Redmine::Host = '' | |
Redmine::APIKey = '' | |
Gitlab.configure do |config| | |
config.endpoint = '' | |
config.private_token = '' | |
end | |
# puts Redmine::IssueStatus.list.inspect | |
# puts Redmine::IssueStatus.by_name('Assigned').inspect | |
# puts Redmine::Project.list.first.categories.inspect | |
# puts Redmine::Project.list.first.category_by_name('gitlab bug').inspect | |
# puts Redmine::Issue.create(Redmine::Project.list.first, 'testing creation from script', 'bleh', :assigned_to_id => 3, :status_id => Redmine::IssueStatus.by_name('Assigned').id, :category_id => Redmine::Project.list.first.category_by_name('gitlab task').id) | |
Gitlab.projects.each do |gitlab_project| | |
puts "iterating over project #{gitlab_project.name}" | |
# First, find a project matching the gitlab one | |
redmine_project = Redmine::Project.by_identifier(gitlab_project.name) | |
next if redmine_project.nil? | |
redmine_issues = redmine_project.issues | |
gitlab_issues = Gitlab.issues(gitlab_project.id) | |
processed_gitlab_issues = [] | |
puts "found #{gitlab_issues.count} issues on gitlab" | |
# Then, iterate through all redmine issues of the project | |
redmine_issues.each do |redmine_issue| | |
puts "processing redmine issue #{redmine_issue.id} #{redmine_issue.subject}" | |
# Skipping non gitlab issues | |
next if redmine_issue.category.nil? or redmine_issue.category['name'].match(/^gitlab/).nil? | |
# Find corresponding assignee in gitlab | |
gitlab_assignee = Gitlab.users.detect { |u| u.email == redmine_issue.assignee.mail } unless redmine_issue.assignee.nil? | |
gitlab_assignee_id = gitlab_assignee ? gitlab_assignee.id : nil | |
puts "gitlab assignee: #{gitlab_assignee.inspect}" | |
# Search for an existing issue | |
existing_issue = gitlab_issues.detect { |gitlab_issue| gitlab_issue.title == redmine_issue.subject} | |
puts "issue already existing on gitlab" if existing_issue | |
if existing_issue # Existing issue, updating status | |
if Time.parse(existing_issue.updated_at) < Time.parse(redmine_issue.updated_on) | |
puts "gitlab issue is older than redmine, updating gitlab issue" | |
processed_gitlab_issues << existing_issue unless existing_issue.nil? | |
Gitlab.edit_issue gitlab_project.id, | |
existing_issue.id, | |
:title => redmine_issue.subject, | |
:description => redmine_issue.description, | |
:assignee_id => gitlab_assignee_id, | |
:labels => redmine_issue.category['name'].gsub(/gitlab/, '').strip, | |
:closed => redmine_issue.status['name'] == 'Closed' | |
else | |
puts "gitlab issue is newer than redmine, skip the update" | |
end | |
else # No existing issue, creating it | |
puts "creating issue on gitlab" | |
created_issue = Gitlab.create_issue gitlab_project.id, | |
redmine_issue.subject, | |
:description => redmine_issue.description, | |
:assignee_id => gitlab_assignee_id, | |
:labels => redmine_issue.category['name'].gsub(/gitlab/, '').strip | |
processed_gitlab_issues << existing_issue unless existing_issue.nil? | |
end | |
end | |
(gitlab_issues - processed_gitlab_issues).each do |gitlab_issue| | |
puts "processing gitlab issue #{gitlab_issue.id} #{gitlab_issue.title}" | |
# Find corresponding assignee in redmine | |
redmine_assignee = Redmine::User.by_email(gitlab_issue.assignee.email) unless gitlab_issue.assignee.nil? | |
redmine_assignee_id = redmine_assignee ? redmine_assignee.id : nil | |
# Search for an existing issue | |
existing_issue = redmine_issues.detect { |redmine_issue| gitlab_issue.title == redmine_issue.subject } | |
puts "issue already existing on redmine" if existing_issue | |
status = case | |
when gitlab_issue.closed | |
'Closed' | |
when gitlab_issue.assignee | |
'Assigned' | |
else | |
'New' | |
end | |
status_id = Redmine::IssueStatus.by_name(status).id | |
if existing_issue # Existing issue, updating status | |
puts "updatig issue on redmine" | |
existing_issue.update :description => gitlab_issue.description, | |
:assigned_to_id => redmine_assignee_id, | |
:status_id => status_id, | |
:done_ratio => gitlab_issue.closed ? '100' : '0' | |
else # No existing issue, creating it | |
puts "creating issue on redmine" | |
Redmine::Issue.create( | |
redmine_project, | |
gitlab_issue.title, | |
gitlab_issue.description, | |
:assigned_to_id => redmine_assignee_id, | |
:status_id => status_id, | |
:category_id => Redmine::Project.list.first.category_by_name("gitlab").id, | |
:done_ratio => gitlab_issue.closed ? '100' : '0' | |
) | |
end | |
end | |
end | |
# puts Redmine::Issue.list.first.inspect | |
# puts Redmine::User.find(3).inspect | |
# puts Redmine::User.find(3).mail | |
# puts Gitlab.users.detect { |u| u.email == Redmine::User.find(3).mail }.inspect | |
# puts Gitlab.users.collect &:email | |
# puts Redmine::Issue.list.first.author.inspect | |
# puts Gitlab.projects.inspect | |
# puts Gitlab.issues.first.title | |
# puts Redmine::Project.list.first.id.inspect | |
# puts Redmine::Issue.list(:limit => 500).count | |
# | |
# puts '===' | |
# puts '===' | |
# puts '===' | |
# | |
# puts Redmine::Project.list.first.issues.inspect | |
# puts Redmine::Issue.find(778).inspect | |
# puts Redmine::Project.by_identifier('bureau-dr').inspect |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment