From f66e89662c091e082bd1d2feb6ac91513ccff273 Mon Sep 17 00:00:00 2001 From: Rafael Cavazin <rafaelcavazin@gmail.com> Date: Sun, 21 Jul 2013 09:59:00 -0400 Subject: [PATCH] Merge branch 'master' of https://github.com/gitblit/gitblit --- src/main/distrib/data/groovy/sendmail-html.groovy | 516 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 516 insertions(+), 0 deletions(-) diff --git a/src/main/distrib/data/groovy/sendmail-html.groovy b/src/main/distrib/data/groovy/sendmail-html.groovy new file mode 100644 index 0000000..2912e56 --- /dev/null +++ b/src/main/distrib/data/groovy/sendmail-html.groovy @@ -0,0 +1,516 @@ +/* + * 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) -- Gitblit v1.9.1