├── app
├── helpers
│ └── git_hook_helper.rb
└── controllers
│ └── gitolite_hook_controller.rb
├── lang
└── en.yml
├── test
├── test_helper.rb
└── functional
│ └── github_hook_controller_test.rb
├── init.rb
├── LICENSE
├── contrib
└── hooks
│ └── post-receive-redmine_gitolite
└── README.rdoc
/app/helpers/git_hook_helper.rb:
--------------------------------------------------------------------------------
1 | module GitHookHelper
2 | end
3 |
--------------------------------------------------------------------------------
/lang/en.yml:
--------------------------------------------------------------------------------
1 | # English strings go here
2 | my_label: "My label"
3 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Load the normal Rails helper
2 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
3 |
4 | # Ensure that we are using the temporary fixture path
5 | Engines::Testing.set_fixture_path
6 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require 'redmine'
2 |
3 | Redmine::Plugin.register :redmine_gitolite_hook do
4 | name 'Redmine Gitolite Hook plugin'
5 | author 'Kah Seng Tay, Jakob Skjerning'
6 | description 'This plugin allows your Redmine installation to receive Gitolite post-receive notifications'
7 | version '0.1.1'
8 | end
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 Kah Seng Tay - Gitolite modifications
2 | Copyright (c) 2010 Jakob Skjerning - Original code
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/contrib/hooks/post-receive-redmine_gitolite:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | HOOK_URL=gitolite_hook
4 | FETCH_URL=sys/fetch_changesets
5 | LOG=/home/git/logs/post-receive.log
6 |
7 | KEY=$(git config hooks.redmine_gitolite.key 2>/dev/null)
8 | if [ -z "$KEY" ]; then
9 | KEY=your_redmine_api_key
10 | fi
11 |
12 | REDMINE_SERVER=$(git config hooks.redmine_gitolite.server 2>/dev/null)
13 | if [ -z "$REDMINE_SERVER" ]; then
14 | REDMINE_SERVER=http://your.redmine.server.com
15 | fi
16 |
17 | REDMINE_PROJECT_ID=$(git config hooks.redmine_gitolite.projectid 2>/dev/null)
18 | if [ -z "$REDMINE_PROJECT_ID" ]; then
19 | REDMINE_PROJECT_ID=$GL_REPO
20 | fi
21 |
22 | CURL_IGNORE_SECURITY_CFG=$(git config --bool hooks.redmine_gitolite.curlignoresecurity 2>/dev/null)
23 | if [ -z "$CURL_IGNORE_SECURITY_CFG" ]; then
24 | CURL_IGNORE_SECURITY_CFG="false"
25 | fi
26 |
27 | case "$CURL_IGNORE_SECURITY_CFG" in
28 | true)
29 | CURL_IGNORE_SECURITY=" -k "
30 | ;;
31 | false)
32 | CURL_IGNORE_SECURITY=" "
33 | ;;
34 | esac
35 |
36 | echo
37 | echo "Notifying Redmine (${REDMINE_SERVER}) about changes to this repo (${GL_REPO} => ${REDMINE_PROJECT_ID})"
38 | echo
39 |
40 | # Read from stdin
41 | while read old new refname; do
42 | echo "Hitting the Redmine Gitolite hook for $old $new $refname"
43 | echo -n "Response: "
44 | curl $CURL_IGNORE_SECURITY -S -s -d "oldrev=$old&newrev=$new&refname=$refname&user=$GL_USER" "$REDMINE_SERVER/$HOOK_URL?project_id=$REDMINE_PROJECT_ID"
45 | echo ""
46 | echo ""
47 | echo "Hitting the Redmine fetch changesets URL"
48 | curl -S -s $CURL_IGNORE_SECURITY "$REDMINE_SERVER/$FETCH_URL?id=$REDMINE_PROJECT_ID&key=$KEY"
49 | echo ""
50 | done
51 |
--------------------------------------------------------------------------------
/app/controllers/gitolite_hook_controller.rb:
--------------------------------------------------------------------------------
1 | require 'open3'
2 |
3 | class GitoliteHookController < ApplicationController
4 |
5 | skip_before_filter :verify_authenticity_token, :check_if_login_required
6 |
7 | def index
8 | repository = find_repository
9 |
10 | # Fetch the changes from Gitolite
11 | update_repository(repository)
12 |
13 | # Fetch the new changesets into Redmine
14 | repository.fetch_changesets
15 |
16 | render(:text => 'OK')
17 | end
18 |
19 | private
20 |
21 | def exec(command)
22 | logger.debug { "GitoliteHook: Executing command: '#{command}'" }
23 | stdin, stdout, stderr = Open3.popen3(command)
24 |
25 | output = stdout.readlines.collect(&:strip)
26 | errors = stderr.readlines.collect(&:strip)
27 |
28 | logger.debug { "GitoliteHook: Output from git:" }
29 | logger.debug { "GitoliteHook: * STDOUT: #{output}"}
30 | logger.debug { "GitoliteHook: * STDERR: #{output}"}
31 | end
32 |
33 | # Fetches updates from the remote repository
34 | def update_repository(repository)
35 | command = "cd '#{repository.url}' && git fetch origin && git reset --soft refs/remotes/origin/master"
36 | exec(command)
37 | end
38 |
39 | # Gets the project identifier from the querystring parameters.
40 | def get_identifier
41 | identifier = params[:project_id]
42 | # TODO: Can obtain 'oldrev', 'newrev', 'refname', 'user' in POST params for further action if needed.
43 | raise ActiveRecord::RecordNotFound, "Project identifier not specified" if identifier.nil?
44 | return identifier
45 | end
46 |
47 | # Finds the Redmine project in the database based on the given project identifier
48 | def find_project
49 | identifier = get_identifier
50 | project = Project.find_by_identifier(identifier.downcase)
51 | raise ActiveRecord::RecordNotFound, "No project found with identifier '#{identifier}'" if project.nil?
52 | return project
53 | end
54 |
55 | # Returns the Redmine Repository object we are trying to update
56 | def find_repository
57 | project = find_project
58 | repository = project.repository
59 | raise TypeError, "Project '#{project.to_s}' ('#{project.identifier}') has no repository" if repository.nil?
60 | raise TypeError, "Repository for project '#{project.to_s}' ('#{project.identifier}') is not a Git repository" unless repository.is_a?(Repository::Git)
61 | return repository
62 | end
63 |
64 | end
65 |
--------------------------------------------------------------------------------
/test/functional/github_hook_controller_test.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 | require 'mocha'
4 |
5 | class GithubHookControllerTest < ActionController::TestCase
6 |
7 | def setup
8 | # Sample JSON post from http://github.com/guides/post-receive-hooks
9 | @json = '{
10 | "before": "5aef35982fb2d34e9d9d4502f6ede1072793222d",
11 | "repository": {
12 | "url": "http://github.com/defunkt/github",
13 | "name": "github",
14 | "description": "You\'re lookin\' at it.",
15 | "watchers": 5,
16 | "forks": 2,
17 | "private": 1,
18 | "owner": {
19 | "email": "chris@ozmm.org",
20 | "name": "defunkt"
21 | }
22 | },
23 | "commits": [
24 | {
25 | "id": "41a212ee83ca127e3c8cf465891ab7216a705f59",
26 | "url": "http://github.com/defunkt/github/commit/41a212ee83ca127e3c8cf465891ab7216a705f59",
27 | "author": {
28 | "email": "chris@ozmm.org",
29 | "name": "Chris Wanstrath"
30 | },
31 | "message": "okay i give in",
32 | "timestamp": "2008-02-15T14:57:17-08:00",
33 | "added": ["filepath.rb"]
34 | },
35 | {
36 | "id": "de8251ff97ee194a289832576287d6f8ad74e3d0",
37 | "url": "http://github.com/defunkt/github/commit/de8251ff97ee194a289832576287d6f8ad74e3d0",
38 | "author": {
39 | "email": "chris@ozmm.org",
40 | "name": "Chris Wanstrath"
41 | },
42 | "message": "update pricing a tad",
43 | "timestamp": "2008-02-15T14:36:34-08:00"
44 | }
45 | ],
46 | "after": "de8251ff97ee194a289832576287d6f8ad74e3d0",
47 | "ref": "refs/heads/master"
48 | }'
49 | @repository = Repository::Git.new
50 | @repository.stubs(:fetch_changesets).returns(true)
51 |
52 | @project = Project.new
53 | @project.stubs(:repository).returns(@repository)
54 | Project.stubs(:find_by_identifier).with('github').returns(@project)
55 |
56 | # Make sure we don't run actual commands in test
57 | Open3.stubs(:popen3)
58 |
59 | Repository.expects(:fetch_changesets).never
60 | end
61 |
62 | def mock_descriptor(kind, contents = [])
63 | descriptor = mock(kind)
64 | descriptor.expects(:readlines).returns(contents)
65 | descriptor
66 | end
67 |
68 | def do_post(payload = nil)
69 | payload = @json if payload.nil?
70 | payload = payload.to_json if payload.is_a?(Hash)
71 | post :index, :payload => payload
72 | end
73 |
74 | def test_should_use_the_repository_name_as_project_identifier
75 | Project.expects(:find_by_identifier).with('github').returns(@project)
76 | @controller.stubs(:exec).returns(true)
77 | do_post
78 | end
79 |
80 | def test_should_update_the_repository_using_git_on_the_commandline
81 | Project.expects(:find_by_identifier).with('github').returns(@project)
82 | @controller.expects(:exec).returns(true)
83 | do_post
84 | end
85 |
86 | def test_should_use_project_identifier_from_request
87 | Project.expects(:find_by_identifier).with('redmine').returns(@project)
88 | @controller.stubs(:exec).returns(true)
89 | post :index, :project_id => 'redmine', :payload => @json
90 | end
91 |
92 | def test_should_downcase_identifier
93 | # Redmine project identifiers are always downcase
94 | Project.expects(:find_by_identifier).with('redmine').returns(@project)
95 | @controller.stubs(:exec).returns(true)
96 | post :index, :project_id => 'ReDmInE', :payload => @json
97 | end
98 |
99 | def test_should_render_ok_when_done
100 | @controller.expects(:exec).returns(true)
101 | do_post
102 | assert_response :success
103 | assert_equal 'OK', @response.body
104 | end
105 |
106 | def test_should_fetch_changesets_into_the_repository
107 | @controller.expects(:exec).returns(true)
108 | @repository.expects(:fetch_changesets).returns(true)
109 | do_post
110 | assert_response :success
111 | assert_equal 'OK', @response.body
112 | end
113 |
114 | def test_should_return_404_if_project_identifier_not_given
115 | assert_raises ActiveRecord::RecordNotFound do
116 | do_post :repository => {}
117 | end
118 | end
119 |
120 | def test_should_return_404_if_project_not_found
121 | assert_raises ActiveRecord::RecordNotFound do
122 | Project.expects(:find_by_identifier).with('foobar').returns(nil)
123 | do_post :repository => {:name => 'foobar'}
124 | end
125 | end
126 |
127 | def test_should_return_500_if_project_has_no_repository
128 | assert_raises TypeError do
129 | project = mock('project', :to_s => 'My Project', :identifier => 'github')
130 | project.expects(:repository).returns(nil)
131 | Project.expects(:find_by_identifier).with('github').returns(project)
132 | do_post :repository => {:name => 'github'}
133 | end
134 | end
135 |
136 | def test_should_return_500_if_repository_is_not_git
137 | assert_raises TypeError do
138 | project = mock('project', :to_s => 'My Project', :identifier => 'github')
139 | repository = Repository::Subversion.new
140 | project.expects(:repository).at_least(1).returns(repository)
141 | Project.expects(:find_by_identifier).with('github').returns(project)
142 | do_post :repository => {:name => 'github'}
143 | end
144 | end
145 |
146 | def test_should_not_require_login
147 | @controller.expects(:exec).returns(true)
148 | @controller.expects(:check_if_login_required).never
149 | do_post
150 | end
151 |
152 | def test_exec_should_log_output_from_git_as_debug
153 | stdout = mock_descriptor('STDOUT', ["output 1\n", "output 2\n"])
154 | stderr = mock_descriptor('STDERR', ["error 1\n", "error 2\n"])
155 | Open3.expects(:popen3).returns(['STDIN', stdout, stderr])
156 |
157 | @controller.logger.expects(:debug).at_least(4)
158 | do_post
159 | end
160 |
161 | end
162 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Redmine Gitolite Hook
2 |
3 | This plugin allows you to update your local Git repositories in Redmine when changes have been pushed to your Gitolite server.
4 |
5 | == Description
6 |
7 | This project is a fork of {koppen/redmine_github_hook}[https://github.com/koppen/redmine_github_hook]
8 | by {Jakob Skjerning}[https://github.com/koppen] with modifications made to support
9 | {Gitolite}[https://github.com/sitaramc/gitolite],
10 | a centralised git server by {Sitaram Chamarty}[https://github.com/sitaramc].
11 |
12 | {Redmine}[http://redmine.org] has supported Git repositories for a long time, allowing you to browse
13 | your code and view your changesets directly in Redmine.
14 | For this purpose, Redmine relies on local clones of the Git repositories.
15 |
16 | If your shared repository is on a remote machine - for example on a Gitolite server - this unfortunately
17 | means a bit of legwork to keep the local, Redmine-accessible repository up-to-date. The common approach
18 | is to set up a cronjob that pulls in any changes with regular intervals and updates Redmine with them.
19 |
20 | That approach works perfectly fine, but is a bit heavy-handed and cumbersome. The Redmine Gitolite
21 | Hook plugin allows Gitolite to notify your Redmine installation when changes have been pushed to a
22 | repository, triggering an update of your local repository and Redmine data only when it is actually
23 | necessary.
24 |
25 |
26 | == Installation
27 |
28 | 1. Installing the plugin
29 | 1. Follow the plugin installation procedure at http://www.redmine.org/wiki/redmine/Plugins.
30 | 1. For convenience, the latest steps for Redmine 1.0.3 are to go to $REDMINE_DIR/vendor/plugins
31 | 2. git clone git://github.com/kahseng/redmine_gitolite_hook.git
32 | 2. Restart your Redmine.
33 | 3. If you already have a local Git repository set up and working from Redmine go to step 3, otherwise continue at step 2.
34 |
35 | 2. Adding a Git repository to a project (note, this should work whether you want to use Redmine Gitolite Hook or not).
36 | Either follow the instructions at http://www.redmine.org/wiki/redmine/HowTo_keep_in_sync_your_git_repository_for_redmine or the ones below:
37 | 1. Go to the directory on your Redmine machine where you want to keep your repository $REDMINE_REPO_DIR,
38 | for example /home/redmine/repositories/.
39 | 2. Get a clone of the Gitolite repository into that location:
40 | 1. git clone git@your_gitolite_server:repository_name
41 | 2. This creates a .git directory at REDMINE_REPO_DIR/repository_name/.git
42 | 3. Open Redmine in your browser and navigate to the Settings for the project you want to add a Git repository to.
43 | 4. Under the Repository tab, choose Git as your SCM and enter the full path to the .git directory from step 2;
44 | $REDMINE_REPO_DIR/repository_name/.git/. Click "Create".
45 | 5. Click the new "Repository" link in the main navigation to verify that your repository integration works -
46 | this might take a while as Redmine is fetching all commits.
47 |
48 | 3. Connecting Gitolite to Redmine
49 | 1. Go to your Gitolite repository on your Gitolite server $GL_REPO_DIR, for example /home/git/repositories/testing.git/.
50 | 2. mv $GL_REPO_DIR/hooks/post-receive.sample $GL_REPO_DIR/hooks/post-receive
51 | 3. Replace the hook file's contents with:
52 |
53 | #!/bin/sh
54 | #
55 | # An example hook script for the "post-receive" event.
56 | #
57 | # The "post-receive" script is run after receive-pack has accepted a pack
58 | # and the repository has been updated. It is passed arguments in through
59 | # stdin in the form
60 | #
61 | # For example:
62 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
63 | #
64 | # see contrib/hooks/ for a sample, or uncomment the next line and
65 | # rename the file to "post-receive".
66 |
67 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email
68 |
69 | REDMINE_SERVER=http://your.redmine.server.com
70 | HOOK_URL=gitolite_hook
71 | FETCH_URL=sys/fetch_changesets
72 | KEY=your_redmine_api_key
73 | LOG=/home/git/logs/post-receive.log
74 |
75 | # Read from stdin
76 | while read old new refname; do
77 | echo "Post-receive hook for this repo: [$GL_REPO] $old $new $refname"
78 | # Hit the Redmine Gitolite hook for this repo
79 | curl -d "oldrev=$old&newrev=$new&refname=$refname&user=$GL_USER" "$REDMINE_SERVER/$HOOK_URL?project_id=$GL_REPO"
80 | # Hit the fetch changesets URL
81 | curl "$REDMINE_SERVER/$FETCH_URL?id=$GL_REPO&key=$KEY"
82 | echo "Done."
83 | echo ""
84 | done
85 |
86 | That's it. Gitolite will now send a HTTP POST to the Redmine Gitolite Hook plugin whenever changes are pushed to the Gitolite server.
87 | The plugin then takes care of pulling the changes to the local repository and updating the Redmine database with them.
88 |
89 | == Assumptions
90 |
91 | * Redmine 0.9 running on a *nix-like system. It will probably work on Redmine 0.8 as well.
92 | * Git available on the commandline.
93 | * You have Gitolite set up and are comfortable with creating hooks.
94 |
95 | == Todos
96 |
97 | * Update tests to work with Gitolite instead of Github
98 |
99 | == License
100 |
101 | Copyright (c) 2010 Kah Seng Tay - Gitolite modifications
102 |
103 | Copyright (c) 2009 Jakob Skjerning - Original
104 |
105 | Permission is hereby granted, free of charge, to any person
106 | obtaining a copy of this software and associated documentation
107 | files (the "Software"), to deal in the Software without
108 | restriction, including without limitation the rights to use,
109 | copy, modify, merge, publish, distribute, sublicense, and/or sell
110 | copies of the Software, and to permit persons to whom the
111 | Software is furnished to do so, subject to the following
112 | conditions:
113 |
114 | The above copyright notice and this permission notice shall be
115 | included in all copies or substantial portions of the Software.
116 |
117 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
118 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
119 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
120 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
121 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
122 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
123 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
124 | OTHER DEALINGS IN THE SOFTWARE.
125 |
--------------------------------------------------------------------------------