Skip to content

Instantly share code, notes, and snippets.

@DavidAntaramian
Last active August 29, 2015 14:08
Show Gist options
  • Save DavidAntaramian/5bd7231119289c57a2ff to your computer and use it in GitHub Desktop.
Save DavidAntaramian/5bd7231119289c57a2ff to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# Redmine-Gitolite Hook for Redmine Git Hosting v 0.7.7
# Copyright (c) 2014 by David Antaramian ([email protected])
# Released under the MIT License (SPDX:MIT;https://spdx.org/licenses/MIT)
#
# Modeled after the original hook by Nicolas Rodriguez, et al.
# Original hook available at:
# https://github.com/jbox-web/redmine_git_hosting/blob/0.7.7/contrib/hooks/post-receive.redmine_gitolite.rb
#
# This script expects to be run inside of a git repository with redminegitolite.*
# configuration settings available via `git config`. It expects to receive lines
# via `STDIN` where each line is of the form
#
# old_value<SP>new_value<SP>refs<LF>
#
# where <SP> is a space and <LF> is a Unix-style line terminator. These lines
# are automatically fed to the script by Git as part of the post-receive hook.
# The script will then post each reference over HTTP/S to the Redmine installation
# specified by the configuration settings.
#
# All output is sent to the user via STDOUT through the use of a Logger object.
require 'digest/sha2'
require 'net/http'
require 'uri'
require 'logger'
# Internal: The logging utitlity for the script which writes to `STDOUT`.
# By default, `STDOUT` is piped through the SSH connection to the user pushing
# the repository upstream. Therefore, anything the logger writes out will show
# directly in the output of whatever utility the user pushing is using
# (whether that be the git command line tool, SourceTree, Eclipse, etc.).
$logger = Logger.new(STDOUT)
# Sets the log output to something like the following
# `Thu Oct 23 15:37:45 EDT 2014 (INFO): Processing new commits`
$logger.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} (#{severity}): #{msg}\n"
end
# Since Gitolite::get_config depends on $logger, we have to check the debug mode
# setting using the `git config` command directly. By default, the logger is
# set to DEBUG, meaning it will output all logging messages. Because get_config
# outputs the configuration settings at the DEBUG level, this would result in
# a large amount of information being output.
#
# If debug mode is not set at the configuration level, we use INFO which outputs
# everything except for DEBUG severity messages
$logger.level = %x[git config redminegitolite.debugmode].chomp == "true" ?
Logger::DEBUG : Logger::INFO
# Public: Namespaces the Config constant and the function that bootstraps the
# constant.
module Gitolite
# Internal: This constructs a hash of configuration key/value pairs provided
# by the `git config` command and returns it to the caller. This method is
# meant to be called only by the Config constant.
#
# Returns a hash of the git configuration settings for the working directory
def self.get_config
config_hash = {}
settings = %x[git config --list]
settings.each_line do |l|
name, value = l.chomp.split('=')
$logger.debug("Gitolite Configuration: #{name} has value #{value}")
config_hash[name] = value
end
return config_hash
end
# Public: A hash of git configuration settings which is bootstrapped
# by #get_config
Config = self.get_config()
end
# Public: Represents a Redmine installation which is able to notify itself
# of updates to repositories.
class Redmine
# Public: The URL of the Redmine installation
attr_reader :url
# Public: Constructs a new Redmine object using the url provided
# via Gitolite::Config in the `redminegitolite.redmineurl` settings
#
# Returns a new Redmine object
def initialize
@url = Gitolite::Config["redminegitolite.redmineurl"]
end
# Public: Notifies the Redmine installation of the updated references
# by constructing a new APIRequest.
#
# repository - A Repository object representing the repository being
# updated
# reference - A Reference object representing the reference that has
# been passed in via STDIN
#
# Returns nothing
def notify(repository, reference)
request = APIRequest.new(self, repository)
request.send(reference)
end
end
# Public: Represents a request to the "API" which handles updates to the
# project's repository on Redmine.
class APIRequest
# Public: Constructs a new APIRequest object
#
# redmine - A Redmine object representing the Redmine installation
# which will receive the updates
# repository - A Repostiroy object representing
#
# Returns a new APIRequest object
def initialize(redmine, repository)
# The server uses this to generate the server side encoded_time
clear_time = Time.new.utc.strftime("%s")
# The repository key acts as a shared secret between the client script
# and the server. The server accepts the clear_time above as a parameter,
# calculates the same hash and then checks it against the result given
# in encoded_time.
encoded_time = Digest::SHA1.hexdigest(clear_time + repository.key)
# The URI of the project where the request will be HTTP POSTed to.
@uri = URI("#{redmine.url}/#{repository.project}")
# The hash of the parameters to be HTTP POSTed
@params = {
clear_time: clear_time,
encoded_time: encoded_time,
}
# Add repository.id to the hash if it exists. The repository.id
# will only be present when the repository being pushed is *not*
# the main repository for the project
@params["repositoryid"] = repository.id if repository.id
end
# Public: Posts the APIRequest to the server
#
# reference - A Reference object representing the the reference that has
# been passed in via STDIN
#
# Returns nothing
def send(reference)
# Add the reference to the parameters using the key refs[] and concatenating
# the values together using commas with no spaces in between
@params["refs[]"] = [reference.old_value, reference.new_value, reference.ref_name].join(',')
$logger.debug("Parameters to be posted #{@params}")
$logger.debug("Posting to #{@uri.host}:#{@uri.port}#{@uri.path}")
# Construct the new HTTP Post object
request = Net::HTTP::Post.new(@uri.path)
# Set the form data to be posted in the body of the request as @params
request.set_form_data(@params)
# Performs the HTTP request and checks the response. SSL is used if the URI
# specifies HTTPS as the scheme.
Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == "https") do |http|
response = http.request(request)
if !response.is_a? Net::HTTPSuccess
$logger.warn("Failed to receive favorable request from Redmine server")
$logger.warn("Server responded with HTTP code #{response.code} (#{response.message})")
$logger.warn("Updating #{reference} failed")
else
$logger.info("Successfuly updated #{reference}")
end
end
end
end
# Public: Represents a git repository for a project on the server
class Repository
# The repository id which may be nil
attr_reader :id
# The project id
attr_reader :project
# The repository key which acts as a shared secret
attr_reader :key
# Public: Constructs a new Repository object
#
# Returns a new Repository object
def initialize
# The repositoryid setting may not exist depending on whether this
# is the main repository for the project or not
if Gitolite::Config["redminegitolite.repositoryid"]
@id = Gitolite::Config["redminegitolite.repositoryid"]
end
@project = Gitolite::Config["redminegitolite.projectid"]
@key = Gitolite::Config["redminegitolite.repositorykey"]
end
# Public: Outputs a text representation of the Repository
#
# Returns a String representation of the object
def to_s
if id
"repository #{id} on project #{project}"
else
"project #{project}'s default repository"
end
end
end
# Public: Represents a reference which has been passed in via STDIN
class Reference
# Public: The old value of the reference
attr_reader :old_value
# Public: The new value of the reference
attr_reader :new_value
# Public: The reference name
attr_reader :ref_name
# Public: Constructs a new Reference object
#
# old_value - The old value of the reference
# new_value - The new value of the reference
# ref_name - The reference name
def initialize(old_value, new_value, ref_name)
@old_value = old_value
@new_value = new_value
@ref_name = ref_name
end
# Public: Outputs a text representation of the Reference
#
# Returns a String representation of the object
def to_s
"#{ref_name} with #{old_value} to #{new_value}"
end
end
repo = Repository.new
redmine = Redmine.new
$logger.info("Processing new commits for #{repo}")
$logger.info("Starting to read pushed references")
# For each line in STDIN ($<) construct a new refernce object and
# notify the Redmine installation of the update.
$<.each do |reference|
old_value, new_value, ref_name = reference.chomp.strip.split
r = Reference.new(old_value, new_value, ref_name)
$logger.debug("Received #{r}")
redmine.notify(repo, r)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment