Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of a JetBrains YouTrack hook for GitBlit. #1084

Merged
merged 1 commit into from
Jun 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/main/distrib/data/groovy/youtrack-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# GitBlit YouTrack Receive Hook

GitBlit receive hook for updating referenced YouTrack issues.

This script has only been tested with the cloud hosted YouTrack instance.

## Usage

Due to limited authentication options when using the YouTrack REST API, you have to store a username and password for an account with appropriate permissions for adding comments to any issue. Hopefully in the future YouTrack will support API keys or similar.

1. Update your `gitblit.properties` file with the following entries:
* `groovy.customFields = "youtrackProjectID=YouTrack Project ID" ` *(or append to existing setting)*
* `youtrack.host = example.myjetbrains.com`
* `youtrack.user = ytUser`
* `youtrack.pass = insecurep@sswordsRus`

(But using your own host and credential info).

2. Copy the `youtrack.groovy` script to the `<gitblit-data-dir>/groovy` scripts directory.
3. In GitBlit, go to a repository, click the *edit* button, then click the *receive* link. In the *post0receive scripts* section you should see `youtrack` as an option. Move it over to the *Selected* column.
4. At the bottom of this same screen should should be a *custom fields* section with a **YouTrack Project ID** field. Enter the YouTrack Project ID associated with the repository.
5. When you commit changes, reference YouTrack issues with `#{projectID}-{issueID}` where `{projectID}` is the YouTrack Project ID, and `{issueID}` is the issue number. For example, to references issue `34` in project `fizz`:

git commit -m'Changed bazinator to fix issue #fizz-34.'

Multiple issues may be referenced in the same commit message.

## Attribution

Much of this script was cobbled together from the example receive hooks in the official [GitBlit](https://github.com/gitblit/gitblit) distribution.
224 changes: 224 additions & 0 deletions src/main/distrib/data/groovy/youtrack.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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<org.eclipse.jgit.transport.ReceiveCommand>
* 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"
}