| /* | |
| * Copyright 2012 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.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.diff.DiffEntry.ChangeType; | |
| import org.eclipse.jgit.lib.Constants; | |
| import org.eclipse.jgit.lib.IndexDiff; | |
| import org.eclipse.jgit.lib.ObjectId; | |
| import org.eclipse.jgit.lib.Repository | |
| import org.eclipse.jgit.lib.Config | |
| import org.eclipse.jgit.patch.FileHeader; | |
| import org.eclipse.jgit.revwalk.RevCommit | |
| import org.eclipse.jgit.revwalk.RevWalk; | |
| import org.eclipse.jgit.transport.ReceiveCommand | |
| import org.eclipse.jgit.transport.ReceiveCommand.Result | |
| 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 org.slf4j.Logger | |
| import groovy.xml.MarkupBuilder | |
| import java.io.IOException; | |
| import java.security.MessageDigest | |
| /** | |
| * Sample Gitblit Post-Receive Hook: sendmail-html | |
| * | |
| * 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. | |
| * | |
| * This script is only executed when pushing to *Gitblit*, not to other Git | |
| * tooling you may be using. | |
| * | |
| * If this script is specified in *groovy.postReceiveScripts* of gitblit.properties | |
| * or web.xml then it will be executed by any repository when it receives a | |
| * push. If you choose to share your script then you may have to consider | |
| * tailoring control-flow based on repository access restrictions. | |
| * | |
| * Scripts may also be specified per-repository in the repository settings page. | |
| * Shared scripts will be excluded from this list of available scripts. | |
| * | |
| * This script is dynamically reloaded and it is executed within it's own | |
| * exception handler so it will not crash another script nor crash Gitblit. | |
| * | |
| * 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 | |
| * user Gitblit User com.gitblit.models.UserModel | |
| * commands JGit commands Collection<org.eclipse.jgit.transport.ReceiveCommand> | |
| * url Base url for Gitblit java.lang.String | |
| * logger Logs messages to Gitblit org.slf4j.Logger | |
| * clientLogger Logs messages to Git client com.gitblit.utils.ClientLogger | |
| * | |
| * Accessing Gitblit Custom Fields: | |
| * def myCustomField = repository.customFields.myCustomField | |
| * | |
| */ | |
| com.gitblit.models.UserModel userModel = user | |
| // Indicate we have started the script | |
| logger.info("sendmail-html hook triggered by ${user.username} for ${repository.name}") | |
| /* | |
| * Primitive email notification. | |
| * This requires the mail settings to be properly configured in Gitblit. | |
| */ | |
| Repository r = gitblit.getRepository(repository.name) | |
| // reuse existing repository config settings, if available | |
| Config config = r.getConfig() | |
| def mailinglist = config.getString('hooks', null, 'mailinglist') | |
| def emailprefix = config.getString('hooks', null, 'emailprefix') | |
| // set default values | |
| def toAddresses = [] | |
| if (emailprefix == null) { | |
| emailprefix = '[Gitblit]' | |
| } | |
| if (mailinglist != null) { | |
| def addrs = mailinglist.split(/(,|\s)/) | |
| toAddresses.addAll(addrs) | |
| } | |
| // add all mailing lists defined in gitblit.properties or web.xml | |
| toAddresses.addAll(gitblit.getStrings(Keys.mail.mailingLists)) | |
| // add all team mailing lists | |
| def teams = gitblit.getRepositoryTeams(repository) | |
| for (team in teams) { | |
| TeamModel model = gitblit.getTeamModel(team) | |
| if (model.mailingLists) { | |
| toAddresses.addAll(model.mailingLists) | |
| } | |
| } | |
| // add all mailing lists for the repository | |
| toAddresses.addAll(repository.mailingLists) | |
| // define the summary and commit urls | |
| def repo = repository.name | |
| def summaryUrl = url + "/summary?r=$repo" | |
| def baseCommitUrl = url + "/commit?r=$repo&h=" | |
| def baseBlobDiffUrl = url + "/blobdiff/?r=$repo&h=" | |
| def baseCommitDiffUrl = url + "/commitdiff/?r=$repo&h=" | |
| def forwardSlashChar = gitblit.getString(Keys.web.forwardSlashCharacter, '/') | |
| if (gitblit.getBoolean(Keys.web.mountParameters, true)) { | |
| repo = repo.replace('/', forwardSlashChar).replace('/', '%2F') | |
| summaryUrl = url + "/summary/$repo" | |
| baseCommitUrl = url + "/commit/$repo/" | |
| baseBlobDiffUrl = url + "/blobdiff/$repo/" | |
| baseCommitDiffUrl = url + "/commitdiff/$repo/" | |
| } | |
| class HtmlMailWriter { | |
| Repository repository | |
| def url | |
| def baseCommitUrl | |
| def baseCommitDiffUrl | |
| def baseBlobDiffUrl | |
| def mountParameters | |
| def forwardSlashChar | |
| def includeGravatar | |
| def shortCommitIdLength | |
| def commitCount = 0 | |
| def commands | |
| def writer = new StringWriter(); | |
| def builder = new MarkupBuilder(writer) | |
| def writeStyle() { | |
| builder.style(type:"text/css", ''' | |
| .table td { | |
| vertical-align: middle; | |
| } | |
| tr.noborder td { | |
| border: none; | |
| padding-top: 0px; | |
| } | |
| .gravatar-column { | |
| width: 5%; | |
| } | |
| .author-column { | |
| width: 20%; | |
| } | |
| .commit-column { | |
| width: 5%; | |
| } | |
| .status-column { | |
| width: 10%; | |
| } | |
| .table-disable-hover.table tbody tr:hover td, | |
| .table-disable-hover.table tbody tr:hover th { | |
| background-color: inherit; | |
| } | |
| .table-disable-hover.table-striped tbody tr:nth-child(odd):hover td, | |
| .table-disable-hover.table-striped tbody tr:nth-child(odd):hover th { | |
| background-color: #f9f9f9; | |
| } | |
| ''') | |
| } | |
| def writeBranchTitle(type, name, action, number) { | |
| builder.div('class' : 'pageTitle') { | |
| builder.span('class':'project') { | |
| mkp.yield "$type " | |
| span('class': 'repository', name ) | |
| if (number > 0) { | |
| mkp.yield " $action ($number commits)" | |
| } else { | |
| mkp.yield " $action" | |
| } | |
| } | |
| } | |
| } | |
| def writeBranchDeletedTitle(type, name) { | |
| builder.div('class' : 'pageTitle', 'style':'color:red') { | |
| builder.span('class':'project') { | |
| mkp.yield "$type " | |
| span('class': 'repository', name ) | |
| mkp.yield " deleted" | |
| } | |
| } | |
| } | |
| def commitUrl(RevCommit commit) { | |
| "${baseCommitUrl}$commit.id.name" | |
| } | |
| def commitDiffUrl(RevCommit commit) { | |
| "${baseCommitDiffUrl}$commit.id.name" | |
| } | |
| def encoded(String path) { | |
| path.replace('/', forwardSlashChar).replace('/', '%2F') | |
| } | |
| def blobDiffUrl(objectId, path) { | |
| if (mountParameters) { | |
| // REST style | |
| "${baseBlobDiffUrl}${objectId.name()}/${encoded(path)}" | |
| } else { | |
| "${baseBlobDiffUrl}${objectId.name()}&f=${path}" | |
| } | |
| } | |
| def writeCommitTable(commits, includeChangedPaths=true) { | |
| // Write commits table | |
| builder.table('class':"table table-disable-hover") { | |
| thead { | |
| tr { | |
| th(colspan: includeGravatar ? 2 : 1, "Author") | |
| th( "Commit" ) | |
| th( "Message" ) | |
| } | |
| } | |
| tbody() { | |
| // Write all the commits | |
| for (commit in commits) { | |
| writeCommit(commit) | |
| if (includeChangedPaths) { | |
| // Write detail on that particular commit | |
| tr('class' : 'noborder') { | |
| td (colspan: includeGravatar ? 3 : 2) | |
| td (colspan:2) { writeStatusTable(commit) } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| def writeCommit(commit) { | |
| def abbreviated = repository.newObjectReader().abbreviate(commit.id, shortCommitIdLength).name() | |
| def author = commit.authorIdent.name | |
| def email = commit.authorIdent.emailAddress | |
| def message = commit.shortMessage | |
| builder.tr { | |
| if (includeGravatar) { | |
| td('class':"gravatar-column") { | |
| img(src:gravatarUrl(email), 'class':"gravatar") | |
| } | |
| } | |
| td('class':"author-column", author) | |
| td('class':"commit-column") { | |
| a(href:commitUrl(commit)) { | |
| span('class':"label label-info", abbreviated ) | |
| } | |
| } | |
| td { | |
| mkp.yield message | |
| a('class':'link', href:commitDiffUrl(commit), " [commitdiff]" ) | |
| } | |
| } | |
| } | |
| def writeStatusLabel(style, tooltip) { | |
| builder.span('class' : style, 'title' : tooltip ) | |
| } | |
| def writeAddStatusLine(ObjectId id, FileHeader header) { | |
| builder.td('class':'changeType') { | |
| writeStatusLabel("addition", "addition") | |
| } | |
| builder.td { | |
| a(href:blobDiffUrl(id, header.newPath), header.newPath) | |
| } | |
| } | |
| def writeCopyStatusLine(ObjectId id, FileHeader header) { | |
| builder.td('class':'changeType') { | |
| writeStatusLabel("rename", "rename") | |
| } | |
| builder.td() { | |
| a(href:blobDiffUrl(id, header.newPath), header.oldPath + " copied to " + header.newPath) | |
| } | |
| } | |
| def writeDeleteStatusLine(ObjectId id, FileHeader header) { | |
| builder.td('class':'changeType') { | |
| writeStatusLabel("deletion", "deletion") | |
| } | |
| builder.td() { | |
| a(href:blobDiffUrl(id, header.oldPath), header.oldPath) | |
| } | |
| } | |
| def writeModifyStatusLine(ObjectId id, FileHeader header) { | |
| builder.td('class':'changeType') { | |
| writeStatusLabel("modification", "modification") | |
| } | |
| builder.td() { | |
| a(href:blobDiffUrl(id, header.oldPath), header.oldPath) | |
| } | |
| } | |
| def writeRenameStatusLine(ObjectId id, FileHeader header) { | |
| builder.td('class':'changeType') { | |
| writeStatusLabel("rename", "rename") | |
| } | |
| builder.td() { | |
| mkp.yield header.oldPath | |
| mkp.yieldUnescaped "<b> -&rt; </b>" | |
| a(href:blobDiffUrl(id, header.newPath), header.newPath) | |
| } | |
| } | |
| def writeStatusLine(ObjectId id, FileHeader header) { | |
| builder.tr { | |
| switch (header.changeType) { | |
| case ChangeType.ADD: | |
| writeAddStatusLine(id, header) | |
| break; | |
| case ChangeType.COPY: | |
| writeCopyStatusLine(id, header) | |
| break; | |
| case ChangeType.DELETE: | |
| writeDeleteStatusLine(id, header) | |
| break; | |
| case ChangeType.MODIFY: | |
| writeModifyStatusLine(id, header) | |
| break; | |
| case ChangeType.RENAME: | |
| writeRenameStatusLine(id, header) | |
| break; | |
| } | |
| } | |
| } | |
| def writeStatusTable(RevCommit commit) { | |
| DiffFormatter formatter = new DiffFormatter(DisabledOutputStream.INSTANCE) | |
| formatter.setRepository(repository) | |
| formatter.setDetectRenames(true) | |
| formatter.setDiffComparator(RawTextComparator.DEFAULT); | |
| def diffs | |
| RevWalk rw = new RevWalk(repository) | |
| if (commit.parentCount > 0) { | |
| RevCommit parent = rw.parseCommit(commit.parents[0].id) | |
| diffs = formatter.scan(parent.tree, commit.tree) | |
| } else { | |
| diffs = formatter.scan(new EmptyTreeIterator(), | |
| new CanonicalTreeParser(null, rw.objectReader, commit.tree)) | |
| } | |
| rw.dispose() | |
| // Write status table | |
| builder.table('class':"plain") { | |
| tbody() { | |
| for (DiffEntry entry in diffs) { | |
| FileHeader header = formatter.toFileHeader(entry) | |
| writeStatusLine(commit.id, header) | |
| } | |
| } | |
| } | |
| } | |
| def md5(text) { | |
| def digest = MessageDigest.getInstance("MD5") | |
| //Quick MD5 of text | |
| def hash = new BigInteger(1, digest.digest(text.getBytes())) | |
| .toString(16) | |
| .padLeft(32, "0") | |
| hash.toString() | |
| } | |
| def gravatarUrl(email) { | |
| def cleaned = email.trim().toLowerCase() | |
| "http://www.gravatar.com/avatar/${md5(cleaned)}?s=30" | |
| } | |
| def writeNavbar() { | |
| builder.div('class':"navbar navbar-fixed-top") { | |
| div('class':"navbar-inner") { | |
| div('class':"container") { | |
| a('class':"brand", href:"${url}", title:"GitBlit") { | |
| img(src:"${url}/gitblt_25_white.png", | |
| width:"79", | |
| height:"25", | |
| 'class':"logo") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| def write() { | |
| builder.html { | |
| head { | |
| link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap.css") | |
| link(rel:"stylesheet", href:"${url}/gitblit.css") | |
| link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap-responsive.css") | |
| writeStyle() | |
| } | |
| body { | |
| writeNavbar() | |
| div('class':"container") { | |
| for (command in commands) { | |
| 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: | |
| def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() | |
| commitCount += commits.size() | |
| if (refType == 'Branch') { | |
| // new branch | |
| writeBranchTitle(refType, ref, "created", commits.size()) | |
| writeCommitTable(commits, true) | |
| } else { | |
| // new tag | |
| writeBranchTitle(refType, ref, "created", 0) | |
| writeCommitTable(commits, false) | |
| } | |
| break | |
| case ReceiveCommand.Type.UPDATE: | |
| def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() | |
| commitCount += commits.size() | |
| // fast-forward branch commits table | |
| // Write header | |
| writeBranchTitle(refType, ref, "updated", commits.size()) | |
| writeCommitTable(commits) | |
| break | |
| case ReceiveCommand.Type.UPDATE_NONFASTFORWARD: | |
| def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() | |
| commitCount += commits.size() | |
| // non-fast-forward branch commits table | |
| // Write header | |
| writeBranchTitle(refType, ref, "updated [NON fast-forward]", commits.size()) | |
| writeCommitTable(commits) | |
| break | |
| case ReceiveCommand.Type.DELETE: | |
| // deleted branch/tag | |
| writeBranchDeletedTitle(refType, ref) | |
| break | |
| default: | |
| break | |
| } | |
| } | |
| } | |
| } | |
| } | |
| writer.toString() | |
| } | |
| } | |
| def mailWriter = new HtmlMailWriter() | |
| mailWriter.repository = r | |
| mailWriter.baseCommitUrl = baseCommitUrl | |
| mailWriter.baseBlobDiffUrl = baseBlobDiffUrl | |
| mailWriter.baseCommitDiffUrl = baseCommitDiffUrl | |
| mailWriter.forwardSlashChar = forwardSlashChar | |
| mailWriter.commands = commands | |
| mailWriter.url = url | |
| mailWriter.mountParameters = gitblit.getBoolean(Keys.web.mountParameters, true) | |
| mailWriter.includeGravatar = gitblit.getBoolean(Keys.web.allowGravatar, true) | |
| mailWriter.shortCommitIdLength = gitblit.getInteger(Keys.web.shortCommitIdLength, 8) | |
| def content = mailWriter.write() | |
| // close the repository reference | |
| r.close() | |
| // tell Gitblit to send the message (Gitblit filters duplicate addresses) | |
| def repositoryName = repository.name.substring(0, repository.name.length() - 4) | |
| gitblit.sendHtmlMail("${emailprefix} ${userModel.displayName} pushed ${mailWriter.commitCount} commits => $repositoryName", | |
| content, | |
| toAddresses) |