Skip to content

Instantly share code, notes, and snippets.

@jakimowicz
Created November 15, 2012 16:22
Show Gist options
  • Select an option

  • Save jakimowicz/4079496 to your computer and use it in GitHub Desktop.

Select an option

Save jakimowicz/4079496 to your computer and use it in GitHub Desktop.

Revisions

  1. jakimowicz created this gist Nov 15, 2012.
    341 changes: 341 additions & 0 deletions redmine gitlab sync
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,341 @@
    #!/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