/* * Copyright 2013 gitblit.com. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import com.gitblit.GitBlit import com.gitblit.Keys import com.gitblit.models.RepositoryModel import com.gitblit.models.TeamModel import com.gitblit.models.UserModel import com.gitblit.utils.JGitUtils import java.text.SimpleDateFormat import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Config import org.eclipse.jgit.transport.ReceiveCommand import org.eclipse.jgit.transport.ReceiveCommand.Result import org.slf4j.Logger import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.IndexDiff; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.util.io.DisabledOutputStream; import java.util.Set; import java.util.HashSet; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; import org.apache.http.protocol.*; import org.apache.http.client.protocol.*; import org.apache.http.client.methods.*; import org.apache.http.impl.client.*; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.util.EntityUtils; /** * GitBlit Post-Receive Hook for YouTrack * * The purpose of this script is to invoke the YouTrack API and update a case when * push is received based. * * The Post-Receive hook is executed AFTER the pushed commits have been applied * to the Git repository. This is the appropriate point to trigger an * integration build or to send a notification. * * If you want this hook script to fail and abort all subsequent scripts in the * chain, "return false" at the appropriate failure points. * * Bound Variables: * gitblit Gitblit Server com.gitblit.GitBlit * repository Gitblit Repository com.gitblit.models.RepositoryModel * receivePack JGit Receive Pack org.eclipse.jgit.transport.ReceivePack * user Gitblit User com.gitblit.models.UserModel * commands JGit commands Collection * url Base url for Gitblit String * logger Logs messages to Gitblit org.slf4j.Logger * clientLogger Logs messages to Git client com.gitblit.utils.ClientLogger * * * Custom Fileds Used by This script * youtrackProjectID - Project ID in YouTrack * * Make sure to add the following to your gitblit.properties file: * groovy.customFields = "youtrackProjectID=YouTrack Project ID" * youtrack.host = example.myjetbrains.com * youtrack.user = ytUser * youtrack.pass = insecurep@sswordsRus */ // Indicate we have started the script logger.info("youtrack hook triggered in ${url} by ${user.username} for ${repository.name}") Repository r = gitblit.getRepository(repository.name) // pull custom fields from repository specific values def youtrackProjectID = repository.customFields.youtrackProjectID if(youtrackProjectID == null || youtrackProjectID.length() == 0) return true; def youtrackHost = gitblit.getString('youtrack.host', 'nohost') def bugIdRegex = gitblit.getString('youtrack.commitMessageRegex', "#${youtrackProjectID}-([0-9]+)") def youtrackUser = gitblit.getString('youtrack.user', 'nouser') def youtrackPass = gitblit.getString('youtrack.pass', 'nopassword') HttpHost target = new HttpHost(youtrackHost, 80, "http"); CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( new AuthScope(target.getHostName(), target.getPort()), new UsernamePasswordCredentials(youtrackUser, youtrackPass)); def httpclient = new DefaultHttpClient(); httpclient.setCredentialsProvider(credsProvider); try { AuthCache authCache = new BasicAuthCache(); BasicScheme basicAuth = new BasicScheme(); authCache.put(target, basicAuth); BasicHttpContext localcontext = new BasicHttpContext(); localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache); for (command in commands) { for( commit in JGitUtils.getRevLog(r, command.oldId.name, command.newId.name).reverse() ) { def bugIds = new java.util.HashSet() def longMsg = commit.getFullMessage() // Grab the second match group and then filter out each numeric ID and add it to array (longMsg =~ bugIdRegex).each{ (it[1] =~ "\\d+").each { bugIds.add(it)} } if(bugIds.size() > 0) { def comment = createIssueComment(command, commit) logger.debug("Submitting youtrack comment:\n" + comment) def encoded = URLEncoder.encode(comment) for(bugId in bugIds ) { def baseURL = "http://${youtrackHost}/youtrack/rest/issue/${youtrackProjectID}-${bugId}/execute?command=&comment=" + encoded def post = new HttpPost(baseURL); clientLogger.info("Executing request " + post.getRequestLine() + " to target " + target); def response = httpclient.execute(target, post, localcontext); logger.debug(response.getStatusLine().toString()); EntityUtils.consume(response.getEntity()); } } } } } finally { r.close() } def createIssueComment(command, commit) { def commits = [commit] // Borrowed code expects a collection. Repository r = gitblit.getRepository(repository.name) // define the summary and commit urls def repo = repository.name def summaryUrl def commitUrl if (gitblit.getBoolean(Keys.web.mountParameters, true)) { repo = repo.replace('/', gitblit.getString(Keys.web.forwardSlashCharacter, '/')).replace('/', '%2F') summaryUrl = url + "/summary/$repo" commitUrl = url + "/commit/$repo/" } else { summaryUrl = url + "/summary?r=$repo" commitUrl = url + "/commit?r=$repo&h=" } // construct a simple text summary of the changes contained in the push def commitBreak = '\n' def commitCount = 0 def changes = '' SimpleDateFormat df = new SimpleDateFormat(gitblit.getString(Keys.web.datetimestampLongFormat, 'EEEE, MMMM d, yyyy h:mm a z')) def table = { def shortSha = it.id.name.substring(0, 8) "* [$commitUrl$it.id.name ${shortSha}] by *${it.authorIdent.name}* on ${df.format(JGitUtils.getCommitDate(it))}\n" + " {cut $it.shortMessage}\n{noformat}$it.fullMessage{noformat}{cut}" } def ref = command.refName def refType = 'branch' if (ref.startsWith('refs/heads/')) { ref = command.refName.substring('refs/heads/'.length()) } else if (ref.startsWith('refs/tags/')) { ref = command.refName.substring('refs/tags/'.length()) refType = 'tag' } switch (command.type) { case ReceiveCommand.Type.CREATE: // new branch changes += "''new $refType $ref created''\n" changes += commits.collect(table).join(commitBreak) changes += '\n' break case ReceiveCommand.Type.UPDATE: // fast-forward branch commits table changes += "''$ref $refType updated''\n" changes += commits.collect(table).join(commitBreak) changes += '\n' break case ReceiveCommand.Type.UPDATE_NONFASTFORWARD: // non-fast-forward branch commits table changes += "''$ref $refType updated [NON fast-forward]''" changes += commits.collect(table).join(commitBreak) changes += '\n' break case ReceiveCommand.Type.DELETE: // deleted branch/tag changes += "''$ref $refType deleted''" break default: break } return "$user.username pushed commits to [$summaryUrl $repository.name]\n$changes" }