Skip to content

Instantly share code, notes, and snippets.

@rbramley
Created March 2, 2013 21:43
Show Gist options
  • Save rbramley/5073413 to your computer and use it in GitHub Desktop.
Save rbramley/5073413 to your computer and use it in GitHub Desktop.
Script to migrate GoogleCode issues
/**
* Copying and distribution of this file, with or without modification,
* are permitted in any medium without royalty provided the copyright
* notice and this notice are preserved. This file is offered as-is,
* without any warranty.
*
* Migrate issues from one Googlecode project to another (e.g. if you need to rename).
* Note (line 39): fetch of issues from source project currently limited to 50
*
* See: http://code.google.com/p/support/wiki/IssueTrackerAPI for API details
*
* @author Robin Bramley, Ixxus Limited (c) 2011
*/
@Grab(group='commons-logging', module='commons-logging', version='1.1.1')
@Grab(group='commons-codec', module='commons-codec', version='1.4')
@Grab(group='org.apache.httpcomponents', module='httpclient', version='4.1.2')
import groovy.xml.MarkupBuilder
import org.apache.http.*
import org.apache.http.client.*
import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.client.methods.*
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.message.BasicNameValuePair
import org.apache.http.util.EntityUtils
// ------------------
// Set these 5 items
def sourceProjectName = 'source-project'
def targetProjectName = 'target-project'
def emailAddress = '[email protected]'
def password = 'foobar'
def sourceScript = 'acme-groovyTest-0.1' // first portion is meant to be a company name
// ------------------
def googleLoginUrl = 'https://www.google.com/accounts/ClientLogin'
def issuesListUrl = "https://code.google.com/feeds/issues/p/${sourceProjectName}/issues/full?max-results=50"
def issuePostUrl = "https://code.google.com/feeds/issues/p/${targetProjectName}/issues/full"
def issuesXmlns = 'http://schemas.google.com/projecthosting/issues/2009'
def atomXmlns = 'http://www.w3.org/2005/Atom'
/** Build an atom feed for an issue */
def buildIssue(entry, issuesXmlns, atomXmlns) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.'atom:entry'('xmlns:atom':atomXmlns,'xmlns:issues':issuesXmlns) {
'atom:title'(entry.title)
'atom:content'(type:'html', entry.content)
'atom:author' {
'atom:name'(entry.author.name)
}
entry.'issues:label'.each {
'issues:label'(it)
}
'issues:owner' {
'issues:username'(entry.'issues:owner'.'issues:username')
}
'issues:state'(entry.'issues:state')
'issues:status'(entry.'issues:status')
}
return writer.toString()
}
/** Build an atom feed for a comment */
def buildComment(entry, issuesXmlns, atomXmlns) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.'atom:entry'('xmlns:atom':atomXmlns,'xmlns:issues':issuesXmlns) {
'atom:content'(type:'html', entry.content)
'atom:author' {
'atom:name'(entry.author.name)
}
'issues:updates' {
entry.'issues:updates'.'issues:label'.each {
'issues:label'(it)
}
'issues:ownerUpdate'(entry.'issues:updates'.'issues:owner'.'issues:username')
'issues:state'(entry.'issues:updates'.'issues:state')
'issues:status'(entry.'issues:updates'.'issues:status')
}
}
return writer.toString()
}
// set up login parameters
NameValuePair accountType = new BasicNameValuePair('accountType', 'GOOGLE')
NameValuePair email = new BasicNameValuePair('Email', emailAddress)
NameValuePair passwd = new BasicNameValuePair('Passwd', password)
NameValuePair service = new BasicNameValuePair('service', 'code')
NameValuePair source = new BasicNameValuePair('source', sourceScript)
List<NameValuePair> params = new ArrayList<NameValuePair>(5)
params.addAll([accountType, email, passwd, service, source])
UrlEncodedFormEntity form = new UrlEncodedFormEntity(params)
HttpPost post = new HttpPost(googleLoginUrl)
post.setEntity(form)
HttpClient httpclient = new DefaultHttpClient()
HttpResponse response = httpclient.execute(post)
// check whether 200 or 403
if (response.getStatusLine().getStatusCode() == 403) {
println response.getStatusLine().getReasonPhrase()
return
}
HttpEntity entity = response.getEntity()
def strings = EntityUtils.toString(entity).split('\n')
// fetch the 'Auth' token from the response
def authToken
strings.each {
if (it.startsWith('Auth=')) {
authToken = it.substring(5)
}
}
// get the Atom feed
HttpGet get = new HttpGet(issuesListUrl)
// Set the header => Authorization: GoogleLogin auth=<b>yourAuthToken</b>
get.setHeader('Authorization', "GoogleLogin auth=${authToken}")
response = httpclient.execute(get)
//TODO: check response.getStatusLine().getStatusCode()
entity = response.getEntity()
def atom = EntityUtils.toString(entity)
// Parse and process the atom feed
feed = new XmlSlurper().parseText(atom).declareNamespace([issues:issuesXmlns])
feed.entry.each { entry ->
issueId = entry.'issues:id'
println "Issue ${issueId} - ${entry.title}"
issueCreationXml = buildIssue(entry, issuesXmlns, atomXmlns)
// post the issue
post = new HttpPost(issuePostUrl)
post.setHeader('Content-type', 'application/atom+xml')
post.setHeader('Authorization', "GoogleLogin auth=${authToken}")
post.setEntity(new StringEntity(issueCreationXml))
response = httpclient.execute(post)
// check for bad request
println "${response.getStatusLine().getStatusCode()} - ${response.getStatusLine().getReasonPhrase()}"
if (response.statusLine.statusCode == 400) {
println EntityUtils.toString(response.entity)
return
}
EntityUtils.consume(response.entity)
/*
* Now get the comments and iterate
*/
def issuesCommentsListUrl = "https://code.google.com/feeds/issues/p/${sourceProjectName}/issues/${issueId}/comments/full"
def issuesCommentsPostUrl = "https://code.google.com/feeds/issues/p/${targetProjectName}/issues/${issueId}/comments/full"
// get the Atom feed
get = new HttpGet(issuesCommentsListUrl)
get.setHeader('Authorization', "GoogleLogin auth=${authToken}")
response = httpclient.execute(get)
println "${response.getStatusLine().getStatusCode()} - ${response.getStatusLine().getReasonPhrase()}"
entity = response.getEntity()
commentsAtom = EntityUtils.toString(entity)
commentsFeed = new XmlSlurper().parseText(commentsAtom).declareNamespace([issues:issuesXmlns])
commentsFeed.entry.each { commEntry ->
commentCreationXml = buildComment(commEntry, issuesXmlns, atomXmlns)
// post the comment
post = new HttpPost(issuesCommentsPostUrl)
post.setHeader('Authorization', "GoogleLogin auth=${authToken}")
post.setHeader('Content-type', 'application/atom+xml')
post.setEntity(new StringEntity(commentCreationXml))
response = httpclient.execute(post)
// check for bad request
println "${response.getStatusLine().getStatusCode()} - ${response.getStatusLine().getReasonPhrase()}"
if (response.statusLine.statusCode == 400) {
println EntityUtils.toString(response.entity)
return
}
EntityUtils.consume(response.entity)
}
}
return // with no noise in GroovyConsole!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment