Skip to content

Commit bbb65e0

Browse files
authored
Merge pull request #1084 from metasim/youtrack-hook
Initial implementation of a JetBrains YouTrack hook for GitBlit.
2 parents 24d577f + 121e2f0 commit bbb65e0

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# GitBlit YouTrack Receive Hook
2+
3+
GitBlit receive hook for updating referenced YouTrack issues.
4+
5+
This script has only been tested with the cloud hosted YouTrack instance.
6+
7+
## Usage
8+
9+
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.
10+
11+
1. Update your `gitblit.properties` file with the following entries:
12+
* `groovy.customFields = "youtrackProjectID=YouTrack Project ID" ` *(or append to existing setting)*
13+
* `youtrack.host = example.myjetbrains.com`
14+
* `youtrack.user = ytUser`
15+
* `youtrack.pass = insecurep@sswordsRus`
16+
17+
(But using your own host and credential info).
18+
19+
2. Copy the `youtrack.groovy` script to the `<gitblit-data-dir>/groovy` scripts directory.
20+
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.
21+
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.
22+
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`:
23+
24+
git commit -m'Changed bazinator to fix issue #fizz-34.'
25+
26+
Multiple issues may be referenced in the same commit message.
27+
28+
## Attribution
29+
30+
Much of this script was cobbled together from the example receive hooks in the official [GitBlit](https://github.com/gitblit/gitblit) distribution.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright 2013 gitblit.com.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import com.gitblit.GitBlit
17+
import com.gitblit.Keys
18+
import com.gitblit.models.RepositoryModel
19+
import com.gitblit.models.TeamModel
20+
import com.gitblit.models.UserModel
21+
import com.gitblit.utils.JGitUtils
22+
import java.text.SimpleDateFormat
23+
import org.eclipse.jgit.lib.Repository
24+
import org.eclipse.jgit.lib.Config
25+
import org.eclipse.jgit.transport.ReceiveCommand
26+
import org.eclipse.jgit.transport.ReceiveCommand.Result
27+
import org.slf4j.Logger
28+
29+
import org.eclipse.jgit.api.Status;
30+
import org.eclipse.jgit.api.errors.JGitInternalException;
31+
import org.eclipse.jgit.diff.DiffEntry;
32+
import org.eclipse.jgit.diff.DiffFormatter;
33+
import org.eclipse.jgit.diff.RawTextComparator;
34+
import org.eclipse.jgit.lib.Constants;
35+
import org.eclipse.jgit.lib.IndexDiff;
36+
import org.eclipse.jgit.lib.ObjectId;
37+
import org.eclipse.jgit.patch.FileHeader;
38+
import org.eclipse.jgit.revwalk.RevWalk;
39+
import org.eclipse.jgit.revwalk.RevCommit;
40+
import org.eclipse.jgit.treewalk.FileTreeIterator;
41+
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
42+
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
43+
import org.eclipse.jgit.util.io.DisabledOutputStream;
44+
45+
import java.util.Set;
46+
import java.util.HashSet;
47+
48+
import org.apache.http.HttpHost;
49+
import org.apache.http.auth.AuthScope;
50+
import org.apache.http.auth.UsernamePasswordCredentials;
51+
import org.apache.http.client.AuthCache;
52+
import org.apache.http.client.CredentialsProvider;
53+
import org.apache.http.protocol.*;
54+
import org.apache.http.client.protocol.*;
55+
import org.apache.http.client.methods.*;
56+
import org.apache.http.impl.client.*;
57+
import org.apache.http.impl.auth.BasicScheme;
58+
import org.apache.http.util.EntityUtils;
59+
60+
61+
/**
62+
* GitBlit Post-Receive Hook for YouTrack
63+
*
64+
* The purpose of this script is to invoke the YouTrack API and update a case when
65+
* push is received based.
66+
*
67+
* The Post-Receive hook is executed AFTER the pushed commits have been applied
68+
* to the Git repository. This is the appropriate point to trigger an
69+
* integration build or to send a notification.
70+
*
71+
* If you want this hook script to fail and abort all subsequent scripts in the
72+
* chain, "return false" at the appropriate failure points.
73+
*
74+
* Bound Variables:
75+
* gitblit Gitblit Server com.gitblit.GitBlit
76+
* repository Gitblit Repository com.gitblit.models.RepositoryModel
77+
* receivePack JGit Receive Pack org.eclipse.jgit.transport.ReceivePack
78+
* user Gitblit User com.gitblit.models.UserModel
79+
* commands JGit commands Collection<org.eclipse.jgit.transport.ReceiveCommand>
80+
* url Base url for Gitblit String
81+
* logger Logs messages to Gitblit org.slf4j.Logger
82+
* clientLogger Logs messages to Git client com.gitblit.utils.ClientLogger
83+
*
84+
*
85+
* Custom Fileds Used by This script
86+
* youtrackProjectID - Project ID in YouTrack
87+
*
88+
* Make sure to add the following to your gitblit.properties file:
89+
* groovy.customFields = "youtrackProjectID=YouTrack Project ID"
90+
* youtrack.host = example.myjetbrains.com
91+
* youtrack.user = ytUser
92+
* youtrack.pass = insecurep@sswordsRus
93+
*/
94+
95+
// Indicate we have started the script
96+
logger.info("youtrack hook triggered in ${url} by ${user.username} for ${repository.name}")
97+
98+
Repository r = gitblit.getRepository(repository.name)
99+
100+
// pull custom fields from repository specific values
101+
def youtrackProjectID = repository.customFields.youtrackProjectID
102+
103+
if(youtrackProjectID == null || youtrackProjectID.length() == 0) return true;
104+
105+
def youtrackHost = gitblit.getString('youtrack.host', 'nohost')
106+
def bugIdRegex = gitblit.getString('youtrack.commitMessageRegex', "#${youtrackProjectID}-([0-9]+)")
107+
def youtrackUser = gitblit.getString('youtrack.user', 'nouser')
108+
def youtrackPass = gitblit.getString('youtrack.pass', 'nopassword')
109+
110+
HttpHost target = new HttpHost(youtrackHost, 80, "http");
111+
CredentialsProvider credsProvider = new BasicCredentialsProvider();
112+
credsProvider.setCredentials(
113+
new AuthScope(target.getHostName(), target.getPort()),
114+
new UsernamePasswordCredentials(youtrackUser, youtrackPass));
115+
def httpclient = new DefaultHttpClient();
116+
117+
httpclient.setCredentialsProvider(credsProvider);
118+
119+
try {
120+
121+
AuthCache authCache = new BasicAuthCache();
122+
BasicScheme basicAuth = new BasicScheme();
123+
authCache.put(target, basicAuth);
124+
BasicHttpContext localcontext = new BasicHttpContext();
125+
localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);
126+
127+
128+
for (command in commands) {
129+
for( commit in JGitUtils.getRevLog(r, command.oldId.name, command.newId.name).reverse() ) {
130+
def bugIds = new java.util.HashSet()
131+
def longMsg = commit.getFullMessage()
132+
// Grab the second match group and then filter out each numeric ID and add it to array
133+
(longMsg =~ bugIdRegex).each{ (it[1] =~ "\\d+").each { bugIds.add(it)} }
134+
135+
if(bugIds.size() > 0) {
136+
def comment = createIssueComment(command, commit)
137+
138+
logger.debug("Submitting youtrack comment:\n" + comment)
139+
140+
def encoded = URLEncoder.encode(comment)
141+
for(bugId in bugIds ) {
142+
def baseURL = "http://${youtrackHost}/youtrack/rest/issue/${youtrackProjectID}-${bugId}/execute?command=&comment=" + encoded
143+
def post = new HttpPost(baseURL);
144+
145+
clientLogger.info("Executing request " + post.getRequestLine() + " to target " + target);
146+
def response = httpclient.execute(target, post, localcontext);
147+
logger.debug(response.getStatusLine().toString());
148+
EntityUtils.consume(response.getEntity());
149+
}
150+
}
151+
}
152+
}
153+
}
154+
finally {
155+
r.close()
156+
}
157+
158+
def createIssueComment(command, commit) {
159+
def commits = [commit] // Borrowed code expects a collection.
160+
Repository r = gitblit.getRepository(repository.name)
161+
// define the summary and commit urls
162+
def repo = repository.name
163+
def summaryUrl
164+
def commitUrl
165+
if (gitblit.getBoolean(Keys.web.mountParameters, true)) {
166+
repo = repo.replace('/', gitblit.getString(Keys.web.forwardSlashCharacter, '/')).replace('/', '%2F')
167+
summaryUrl = url + "/summary/$repo"
168+
commitUrl = url + "/commit/$repo/"
169+
} else {
170+
summaryUrl = url + "/summary?r=$repo"
171+
commitUrl = url + "/commit?r=$repo&h="
172+
}
173+
174+
// construct a simple text summary of the changes contained in the push
175+
def commitBreak = '\n'
176+
def commitCount = 0
177+
def changes = ''
178+
179+
SimpleDateFormat df = new SimpleDateFormat(gitblit.getString(Keys.web.datetimestampLongFormat, 'EEEE, MMMM d, yyyy h:mm a z'))
180+
181+
def table = {
182+
def shortSha = it.id.name.substring(0, 8)
183+
"* [$commitUrl$it.id.name ${shortSha}] by *${it.authorIdent.name}* on ${df.format(JGitUtils.getCommitDate(it))}\n" +
184+
" {cut $it.shortMessage}\n{noformat}$it.fullMessage{noformat}{cut}"
185+
}
186+
187+
def ref = command.refName
188+
def refType = 'branch'
189+
if (ref.startsWith('refs/heads/')) {
190+
ref = command.refName.substring('refs/heads/'.length())
191+
} else if (ref.startsWith('refs/tags/')) {
192+
ref = command.refName.substring('refs/tags/'.length())
193+
refType = 'tag'
194+
}
195+
196+
switch (command.type) {
197+
case ReceiveCommand.Type.CREATE:
198+
// new branch
199+
changes += "''new $refType $ref created''\n"
200+
changes += commits.collect(table).join(commitBreak)
201+
changes += '\n'
202+
break
203+
case ReceiveCommand.Type.UPDATE:
204+
// fast-forward branch commits table
205+
changes += "''$ref $refType updated''\n"
206+
changes += commits.collect(table).join(commitBreak)
207+
changes += '\n'
208+
break
209+
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD:
210+
// non-fast-forward branch commits table
211+
changes += "''$ref $refType updated [NON fast-forward]''"
212+
changes += commits.collect(table).join(commitBreak)
213+
changes += '\n'
214+
break
215+
case ReceiveCommand.Type.DELETE:
216+
// deleted branch/tag
217+
changes += "''$ref $refType deleted''"
218+
break
219+
default:
220+
break
221+
}
222+
223+
return "$user.username pushed commits to [$summaryUrl $repository.name]\n$changes"
224+
}

0 commit comments

Comments
 (0)