Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 4fb360102d64bf341a493b7c543144d8c442c91c 0 parents
@jeremyevans authored
Showing with 255 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +18 −0 MIT-LICENSE
  3. +78 −0 README
  4. +158 −0 gcit2ghi.rb
1  .gitignore
@@ -0,0 +1 @@
+/xml
18 MIT-LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2011 Jeremy Evans
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
78 README
@@ -0,0 +1,78 @@
+gcit2ghi.rb
+-----------
+
+This is a simple script to convert from Google Code Issue Tracker to
+GitHub Issues.
+
+Usage
+-----
+
+ruby gcit2ghi.rb project user repo password
+
+project - Google Code project
+user - GitHub user name
+repo - GitHub repository name
+password - GitHub user password
+
+If you have more than 500 issues or more than 500 comments for an
+issue, modify the MAX_RESULTS constant in gcit2ghi.rb.
+
+Requirements
+------------
+
+1) ruby - Programming Language
+2) nokogiri - For parsing the Google Code XML files
+3) json - For serializing the GitHub API calls
+4) rest-client - For submitting the GitHub API calls
+
+How It Works
+------------
+
+1) Downloads the Issue Tracker feed from Google Code (caching it
+ locally in a created xml subdirectory). Parses out all issues.
+
+2) For each issue, downloads the Issue Comments feed from Google
+ Code (caching it locally) and parses out all comments for the
+ issue.
+
+3) Does some preprocessing of the parsed out entries to add to
+ the body of issues and comments so that the GitHub issues will
+ be able to tell the which Google Code user opened the issue,
+ the creation and closed date of the issue, and the Google Code
+ user and posting date for each comment on the issue.
+
+4) Uploads each issue to GitHub, potentially marks the issue as
+ closed if it was closed on Google Code, and uploads each comment
+ related to the issue.
+
+Caveats
+-------
+
+There is very little error handling done. There are no descriptive
+comments in the source code.
+
+Files uploaded to Google Code are not copied.
+
+Labels and milestones are not copied, only the title and body of
+issues and the body of comments. The GitHub assignee is set to the
+user you provided.
+
+If you have a problem with the script after a partial import, you can
+open the script and uncomment out the lines that delete existing
+issues. You shouldn't do this if you want to keep any existing
+issues, though.
+
+This was used to convert the ruby-sequel Google Code issues to
+jeremyevans/sequel on GitHub. I don't plan on using it again or
+improving it, so if you want to make improvements, please fork.
+
+License
+-------
+
+This code is licensed under the MIT license. See the MIT-LICENSE
+file for details.
+
+Author
+------
+
+Jeremy Evans <code@jeremyevans.net>
158 gcit2ghi.rb
@@ -0,0 +1,158 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'nokogiri'
+require 'open-uri'
+require 'cgi'
+require 'json'
+require 'rest_client'
+
+mkdir("xml") unless File.directory?("xml")
+$stdout.sync = true
+
+class GCIT2GHI
+ MAX_RESULTS = 500
+ attr_reader :project, :user, :repo, :password, :entries, :resource
+
+ def initialize(project, user, repo, password)
+ raise "No project name" unless project && !project.empty?
+ @project, @user, @repo, @password= project, user, repo, password
+ end
+
+ def gcit_issues_url
+ "https://code.google.com/feeds/issues/p/#{project}/issues/full?max-results=#{MAX_RESULTS}"
+ end
+
+ def gcit_comments_url(issue_id)
+ "https://code.google.com/feeds/issues/p/#{project}/issues/#{issue_id}/comments/full?max-results=#{MAX_RESULTS}"
+ end
+
+ def ghi_issues_url
+ "https://api.github.com/repos/#{user}/#{repo}/issues"
+ end
+
+ def namespaces
+ @namespaces ||= Hash[*issues_doc.namespaces.to_a.map{|k, v| [k.gsub(/\Axmlns(:)?/){$1 ? '' : 'atom'}, v]}.flatten]
+ end
+
+ def q(doc, query)
+ doc.xpath(query, namespaces)
+ end
+
+ def t(doc, query)
+ q(doc, query).inner_text
+ end
+
+ def uh(doc, query)
+ CGI.unescapeHTML(t(doc, query))
+ end
+
+ def issues_doc
+ return @issues_doc if @issues_doc
+ filename = "xml/#{project}.issues.xml"
+ unless File.exist?(filename)
+ File.open(filename, 'wb'){|f| f.write(open(gcit_issues_url).read)}
+ end
+ @issues_doc = Nokogiri::XML(File.new(filename))
+ end
+
+ def comments_doc(issue_id)
+ filename = "xml/#{project}.issue-#{issue_id}.xml"
+ unless File.exist?(filename)
+ File.open(filename, 'wb'){|f| f.write(open(gcit_comments_url(issue_id)).read)}
+ end
+ Nokogiri::XML(File.new(filename))
+ end
+
+ def convert
+ entries = q(issues_doc, '/atom:feed/atom:entry')
+ print "Parsing issues XML file..."
+ @entries = entries.map do |e|
+ {
+ :id=>t(e, 'issues:id'),
+ :author=>t(e, 'atom:author/atom:name'),
+ :published=>t(e, 'atom:published'),
+ :closed=>t(e, 'issues:closedDate'),
+ :state=>t(e, 'issues:state'),
+ :json => {
+ "title"=>t(e, 'atom:title'),
+ "body"=>uh(e, 'atom:content'),
+ "assignee"=>user,
+ }
+ }
+ end
+ puts "done (#{entries.length} issues)"
+
+ print "Getting comments for each issue: "
+ @entries.each do |e|
+ cdoc = q(comments_doc(e[:id]), '/atom:feed/atom:entry')
+ e[:num_comments] = t(cdoc, "//openSearch:totalResults")
+ e[:comments] = cdoc.map do |c|
+ {
+ :author=>t(c, 'atom:author/atom:name'),
+ :published=>t(c, 'atom:published'),
+ :json => {
+ 'body'=>uh(c, 'atom:content')
+ }
+ }
+ end
+ print "."
+ end
+ puts 'done'
+
+ print "Preprocessing issues and comments: "
+ @entries.each do |e|
+ e[:json]['body'] << "\n\nGoogle Code Info:\nIssue #: #{e[:id]}\nAuthor: #{e[:author]}\nCreated On: #{e[:published]}\nClosed On: #{e[:closed]}"
+ e[:comments].each do |c|
+ c[:json]['body'] << "\n\nGoogle Code Info:\nAuthor: #{c[:author]}\nCreated On: #{c[:published]}"
+ end
+ end
+ puts 'done'
+
+ r = @resource = RestClient::Resource.new(ghi_issues_url, :user=>user, :password=>password)
+ begin
+=begin
+ print "Deleting existing open issues: "
+ while !(existing = JSON.parse(r.get.body)).empty?
+ existing.map{|j| j['number']}.each do |i|
+ r[i.to_s].delete
+ print '.'
+ end
+ end
+ puts 'done'
+ print "Deleting existing closed issues: "
+ cr = RestClient::Resource.new("#{ghi_issues_url}?state=closed", :user=>user, :password=>password)
+ while !(existing = JSON.parse(cr.get.body)).empty?
+ existing.map{|j| j['number']}.each do |i|
+ r[i.to_s].delete
+ print '.'
+ end
+ end
+ puts 'done'
+=end
+ print "Uploading issues (|), closing issues (/), and uploading comments (.): "
+ @entries.each do |e|
+ res = r.post(e[:json].to_json, :content_type=>:json, :accept=>:json)
+ number = JSON.parse(res.body)['number'].to_s
+ print "|"
+ if e[:state] == 'closed'
+ r[number].post({'state'=>'closed'}.to_json, :content_type=>:json, :accept=>:json)
+ print "/"
+ end
+ e[:comments].each do |c|
+ r["#{number}/comments"].post(c[:json].to_json, :content_type=>:json, :accept=>:json)
+ print "."
+ end
+ end
+ puts 'done'
+ rescue RestClient::Exception => ex
+ puts "Error!!"
+ puts ex
+ puts ex.response
+ puts ex.backtrace
+ exit(1)
+ end
+ end
+end
+
+GCIT2GHI.new(*ARGV).convert if __FILE__ == $0
Please sign in to comment.
Something went wrong with that request. Please try again.